Appearance
并发工具包-线程安全的List、Map、Set
看在前面
三大并发类:Concurrent、CopyOnWrite、Blocking
Java并发包中的集合从线程安全实现方式而言可分为三类:Concurrent、CopyOnWrite、Blocking。
Concurrent 类的集合基于 lock-free 的方式实现。严格来说,它们是真正的并发。适合实现高吞吐量。
CopyOnWrite 类的集合顾名思义,会在改变集合的操作中拷贝原数据。然后用新的内部集合对象替换原内部对象。
Blocking 类的集合则通过锁(ReentrantLock)实现。它们会提供 “等待性” 的方法。
Concurrent 的代价
虽然 Concurrent 类的集合没有 CopyOnWrite 那么中的修改开销,但这是有代价的:
- Concurrent 集合的遍历一致性弱。
在利用迭代器遍历时,如果容器发生修改,迭代器可以继续遍历,不会抛出 ConcurrentModificationException。在 HashMap 中则会抛出此异常(也就是 fail-fast 机制),[快速失败(fail—fast)和安全失败(fail—safe)]((原链已失效 2026-10,内容已在本篇/本库)%E8%B4%A5%EF%BC%88fail%E2%80%94fast%EF%BC%89%E5%92%8C%E5%AE%89%E5%85%A8%E5%A4%B1%E8%B4%A5%EF%BC%88fail%E2%80%94safe%EF%BC%89.md)
因为是弱一致性,所以 size 这类操作未必准确。
读取的性能也具有不确定性。
List
- CopyOnWriteArrayList
CopyOnWrite 是指对该集合的任何修改操作都会:拷贝原数组,修改后替换原数组;以此达到线程安全的目的。 这种数据结构适合 读多写少 的场景。因为修改开销大。可直接查看其 add() 方法了解实现原理:
java
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}Map
ConcurrentHashMap vs ConcurrentSkipListMap
ConcurrentHashMap 的存取速度更快
ConcurrentSkipListMap 的元素是经过排序的(它实现了接口 SortedMap)
HashMap 有对应的 ConcurrentHashMap,而 TreeMap 没有对应的 ConcurrentTreeMap。
TreeMap 也实现了接口 SortedMap,它的元素也是经过排序的,JDK 却又造出一个 ConcurrentSkipListMap。
因为 TreeMap 内部基于复杂的红黑树,很难在并发场景中实现合理粒度的同步。SkipList 的内部结构更简单,实现增删元素时的线程安全开销更小。但是会占用更多空间。
Set
- 不存在的 ConcurrentHashSet
不知道为啥没有一个对应的 ConcurrentHashSet。虽然可以通过JDK自带的 Collections.newSetFromMap() 方法创建一个基于 ConcurrentHashMap 的线程安全 Set。但是一般来说有一个对称的语义是好的设计。HashMap - HashSet, ConcurrentHashMap - ConcurrentHashSet。Guava 中的 Sets.newConcurrentHashSet() 内部就是通过上述方式实现的:
java
public static Set newConcurrentHashSet() {
return Collections.newSetFromMap(new ConcurrentHashMap<E, Boolean>());
}- ConcurrentSkipListSet
就像 HashSet 和 HashMap 的关系,ConcurrentSkipListSet 内部用 ConcurrentSkipListMap 实现(value 为 Boolean.TRUE)
- CopyOnWriteArraySet
CopyOnWriteArraySet 内部用 CopyOnWriteArrayList 实现。适用于数据量少、读操作远多于写操作的场景。在遍历的时候应避免多个线程操作其引用。
线程安全的 LinkedHashMap
LinkedHashMap 经常被用于实现 LRU缓存(Least Recently Used)。很遗憾的是JDK中没有对应的线程安全实现。对性能要求高的场景中,Collections.synchronizedMap() 之类的外层一刀切式同步方案又不合适。这时可以考虑一个第三方的实现方案:guava 的 CacheBuilder。例:
java
Map<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(100)
.<String, String>build()
.asMap();pom.xml如下:
java
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.0-jre</version>
</dependency>