欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-04-20 21:13:43
...

[本文是我对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具有类似的特点.