ConcurrentHashMap和CopyOnWriteArrayList--Java Concurrency In Practice C05读书笔记_JAVA_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > JAVA > ConcurrentHashMap和CopyOnWriteArrayList--Java Concurrency In Practice C05读书笔记

ConcurrentHashMap和CopyOnWriteArrayList--Java Concurrency In Practice C05读书笔记

 2012/4/5 13:24:25  coolxing  程序员俱乐部  我要评论(0)
  • 摘要:[本文是我对JavaConcurrencyInPractice5.2的归纳和总结.转载请注明作者和出处,如有谬误,欢迎在评论中指正.]ConcurrentHashMap类我们可以使用Collections.synchronizedMap()方法包装HashMap得到线程安全的Map,但是如前所述,这样会带来很大的性能损失.JDK5之后我们有了新的选择--ConcurrentHashMap.ConcurrentHashMap具有如下特点:1.ConcurrentHashMap具有更好的并发性能
  • 标签:笔记 读书笔记 has Map list Hash Java

[本文是我对Java Concurrency In Practice 5.2的归纳和总结. ?转载请注明作者和出处, ?如有谬误, 欢迎在评论中指正. ]

ConcurrentHashMap类

我们可以使用Collections.synchronizedMap()方法包装HashMap得到线程安全的Map, 但是如前所述, 这样会带来很大的性能损失. JDK5之后我们有了新的选择--ConcurrentHashMap. ConcurrentHashMap具有如下特点:

1. ConcurrentHashMap具有更好的并发性能. ConcurrentHashMap是线程安全的, 但是其同步策略和SynchronizedMap有很大不同. ConcurrentHashMap在read时几乎不用加锁, 而write时使用的是细粒度的分段锁, ConcurrentHashMap甚至可以做到并发write.?

2. 由于ConcurrentHashMap的分段加锁机制, 使用ConcurrentHashMap类时, 调用方无法再自行加锁.

3. 由于调用方无法自行加锁, 因此ConcurrentHashMap类提供了一些常见的复合操作. 如put-If-Absent,remove-if-equal, replace-if-equal等:

// 只有key不是集合中的键时才插入该键值对

V putIfAbsent(K key, V value);?

// 集合中存在该键值对时才删除

boolean remove(K key, V value);?

// 只有key和oldValue是集合中的键值对是才进行替换

boolean replace(K key, V oldValue, V newValue);?

// 只有key是集合中的key时才进行替换

V replace(K key, V newValue);

4. 迭代时不需要调用方进行额外的同步. ConcurrentHashMap使用的迭代器被称为weakly consistent(弱一致)迭代器, 弱一致迭代器不会在迭代期间抛出ConcurrentModificationException异常. 迭代开始后, 如果其他线程删除了ConcurrentHashMap集合的某个元素, 且被删除的元素尚未由next方法返回, 则该元素就不会被迭代器返回给调用方. 如果迭代开始后其他线程往ConcurrentHashMap集合中插入了新的元素, 那么新的元素可能会也可能不会被返回给调用方. 无论如何, 弱一致迭代器都保证不会将同一个元素多次返回给调用方.?

5. 调用ConcurrentHashMap对象的size, isEmpty等方法时(这些方法是针对整体Map的操作), 性能比较差. ConcurrentHashMap适合在要求高并发高性能的场合下使用, 在这些场景下, size或者isEmpty等方法用处不大, 这是可以接受的权衡.

ConcurrentHashMap的实现机制可参考http://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/index.html?ca=drs-

除了确实需要额外加锁的场景, ConcurrentHashMap都是比SynchronizedMap更好的选择.

?

CopyOnWriteArrayList类

同ArrayList一样, CopyOnWriteArrayList底层使用数组存储数据. CopyOnWriteArrayList中的数组定义为volatile, 以保证线程间的可见性. CopyOnWriteArrayList处理写操作(add, remove, set)时, 都会先copy一份数组, 然后在新的数组上进行写操作. 例如add方法:

public boolean add(E e) {
	final ReentrantLock lock = this.lock;
	lock.lock();
	try {
		// getArray返回的原先的底层数组
		Object[] elements = getArray();
		int len = elements.length;
		// copy数组中的数据
		Object[] newElements = Arrays.copyOf(elements, len + 1);
		// 加入新数据
		newElements[len] = e;
		// 改变底层数组
		setArray(newElements);
		return true;
	} finally {
		lock.unlock();
	}
}

这样的写操作肯定会导致性能大幅下降, 尤其是底层数组中包含很多数据的时候. 但是会带来一个优点: 处理读操作(get, iterator, contains等)时不需要进行同步和加锁. 由于读操作是针对当前的数组进行的, 如果读操作过程中其他线程并发修改了CopyOnWriteArrayList对象, 不会影响到当前数组. 所以读操作肯定是线程安全的:

public E get(int index) {
	return (E)(getArray()[index]);
}
public boolean contains(Object o) {
	Object[] elements = getArray();
	return indexOf(o, elements, 0, elements.length) >= 0;
}

迭代也是读操作操作的一种:

private static class COWIterator<E> implements ListIterator<E> {
	// 底层数组的快照
	private final Object[] snapshot;
	// 游标
	private int cursor;

	private COWIterator(Object[] elements, int initialCursor) {
		cursor = initialCursor;
		snapshot = elements;
	}

	public boolean hasNext() {
		return cursor < snapshot.length;
	}

	public boolean hasPrevious() {
		return cursor > 0;
	}

	public E next() {
		if (!hasNext())
			throw new NoSuchElementException();
		return (E) snapshot[cursor++];
	}

	public E previous() {
		if (!hasPrevious())
			throw new NoSuchElementException();
		return (E) snapshot[--cursor];
	}

	public int nextIndex() {
		return cursor;
	}

	public int previousIndex() {
		return cursor - 1;
	}

	/**
	 * 迭代器不支持remove操作
	 */
	public void remove() {
		throw new UnsupportedOperationException();
	}

	/**
	 * 迭代器不支持set操作
	 */
	public void set(E e) {
		throw new UnsupportedOperationException();
	}

	/**
	 * 迭代器不支持add操作
	 */
	public void add(E e) {
		throw new UnsupportedOperationException();
	}
}

创建迭代器时, 迭代器就初始化了当前数组的快照. 就算迭代期间进行了写操作, 也不会影响到迭代器中的snapshot数组. 所以CopyOnWriteArrayList返回的迭代器只反应迭代发生时CopyOnWriteArrayList对象所持有的集合, 迭代期间发生的改变不会反应出来.

总结:

1. CopyOnWriteArrayList是线程安全的, 且处理读操作不需要进行同步和加锁. 所以读操作具有很好的并发性.

2. CopyOnWriteArrayList的写操作是代价很大的, 所以CopyOnWriteArrayList只适用于读操作频率远远大于写操作频率的场景.

3. CopyOnWriteArrayList无法在调用方进行额外加锁. 同时CopyOnWriteArrayList也提供了一些常用的复合操作, 如putIfAbsent等.

4. CopyOnWriteArrayList的迭代只能反应迭代开始时CopyOnWriteArrayList对象所持有的集合. 迭代期间不会抛出ConcurrentModificationException异常, 调用方不需要进行额外的加锁(实际上也没有进行).

CopyOnWriteArraySet和CopyOnWriteArrayList具有类似的特点.

?

发表评论
用户名: 匿名