[本文是我对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具有类似的特点.
分享到:
相关推荐
6. **并发集合**:Java提供了线程安全的集合,如`ConcurrentHashMap`、`CopyOnWriteArrayList`等,这些集合在并发环境中性能优异。书中会分析它们的设计原理和使用场景。 7. **线程池**:`ExecutorService`是线程池...
《Java Concurrency in Practice》是Java并发编程领域的一本经典著作,由Brian Goetz、Tim Peierls、Joshua Bloch、Joseph Bowles和Doug Lea等专家共同编写。这本书深入探讨了Java平台上的多线程和并发编程,旨在...
这里的"java_concurrency_in_practice_source"源代码正是书中实例的实现,它涵盖了Java多线程编程中的关键概念和技术。 1. **线程基础**:Java中创建线程有两种方式,一是通过`Thread`类的子类,二是实现`Runnable`...
首先,"Java Concurrency in Practice"是Java并发编程的经典之作,由Brian Goetz、Tim Peierls、Joshua Bloch、David Holmes和Doug Lea合著。这本书提供了一套实用的指导原则、设计模式和最佳实践,帮助Java开发者...
本笔记将深入探讨《Java Concurrency In Practice》这本书中的核心概念,结合Guava库的实际使用案例,帮助读者理解并掌握Java并发编程的精髓。 首先,我们来了解Java并发的基础知识。Java提供了丰富的并发工具类,...
《Java Concurrency In Practice》是一本关于Java并发编程的经典著作,由Brian Göetz、Tim Peierls、Joshua Bloch、Joseph Bowbeer、David Holmes和Doug Lea共同编写。本书深入探讨了Java平台上的多线程编程技巧,...
3. **并发集合与并发容器**:涵盖了Java并发集合框架,包括线程安全的ArrayList、LinkedList、HashMap等,并介绍了ConcurrentHashMap、CopyOnWriteArrayList等高效率的并发容器。 4. **并发工具**:讨论了Executor...
《Java Concurrency in Practice》是Java并发编程领域的一本经典著作,由Brian Goetz、Tim Peierls、Joshua Bloch、Joe Bowbeer、David Holmes和Doug Lea合著,国内有热心人士进行了中文翻译,使得更多的中国开发者...
《Java Concurrency in Practice》是Java并发编程领域的一本经典著作,由Brian Goetz、Tim Peierls、Joshua Bloch、David Holmes和Doug Lea等多位Java并发领域的专家共同编写。这本书深入浅出地讲解了Java编程中的多...
《Java Concurrency in Practice》是由Java并发库的主要开发者Doug Lea撰写的一本经典书籍,它深入探讨了Java编程中的多线程和并发编程技术。这本书是Java开发者掌握并发编程必备的参考文献,对于理解如何在Java环境...
《Java Concurrency in Practice》是Java并发编程领域的一本经典著作,由Brian Goetz、Tim Peierls、Joshua Bloch、David Holmes和Doug Lea合著。这本书深入探讨了如何在Java环境中有效地设计和实现多线程程序,强调...
- **并发集合**:Java提供了多种并发集合类,如`ConcurrentHashMap`等,用于解决多线程环境下数据结构的安全访问问题。 - **CompletableFuture**:这是一个强大的异步编程工具,可以简化异步操作的编写过程。书中...
《Java Concurrency in Practice》是Java并发编程领域的一本权威著作,由Brian Goetz、Tim Peierls、Joshua Bloch、David Holmes和Doug Lea等多位Java并发领域的专家共同编写。这本书深入探讨了Java平台上的多线程和...
《Java Concurrency in Practice》是Java并发编程领域的一本经典著作,由Brian Goetz、Tim Peierls、Joshua Bloch、David Holmes和Doug Lea合著。这本书深入浅出地探讨了Java平台上的多线程和并发编程,提供了丰富的...