当前位置:首页 > 开发 > 编程语言 > Java > 正文

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

发表于: 2012-03-31   作者:coolxing   来源:转载   浏览:
摘要: [本文是我对Java Concurrency In Practice 5.2的归纳和总结.  转载请注明作者和出处,  如有谬误, 欢迎在评论中指正. ] ConcurrentHashMap类 我们可以使用Collections.synchronizedMap()方法包装HashMap得到线程安全的Map, 但是如前所述, 这样会带来很大的性能损失. JDK5之后我们有了新

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

 

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

  • 0

    开心

    开心

  • 0

    板砖

    板砖

  • 0

    感动

    感动

  • 0

    有用

    有用

  • 0

    疑问

    疑问

  • 0

    难过

    难过

  • 0

    无聊

    无聊

  • 0

    震惊

    震惊

版权所有 IT知识库 CopyRight © 2009-2015 IT知识库 IT610.com , All Rights Reserved. 京ICP备09083238号