在多线程编程中,共享数据结构的线程安全是一个关键问题。传统的集合类(如 HashMap
、ArrayList
)并不是线程安全的,如果在并发环境下直接使用,可能会导致数据不一致、死锁等问题。为了解决这个问题,Java 提供了一套线程安全的并发集合类,它们都位于 java.util.concurrent
包中。
本文将详细介绍 Java 中常见的并发集合类,包括它们的实现原理、使用场景以及性能对比,帮助你更好地选择适合的并发集合来提升程序的并发性能。
Java 提供的并发集合类主要分为以下几类:
集合类 | 用途 | 线程安全机制 |
---|---|---|
ConcurrentHashMap |
线程安全的 Map 实现 | 分段锁(JDK 7) / CAS + synchronized(JDK 8+) |
ConcurrentSkipListMap |
有序的线程安全 Map | 基于跳表实现 |
ConcurrentSkipListSet |
有序的线程安全 Set | 基于跳表 |
CopyOnWriteArrayList |
线程安全的 List | 写时复制 |
CopyOnWriteArraySet |
线程安全的 Set | 写时复制 |
ConcurrentLinkedQueue |
线程安全的无界队列 | 非阻塞(CAS) |
ConcurrentLinkedDeque |
线程安全的双端队列 | 非阻塞 |
BlockingQueue 系列 |
阻塞队列 | 锁或条件变量 |
LinkedBlockingQueue |
基于链表的阻塞队列 | 可配置锁分离 |
ArrayBlockingQueue |
基于数组的阻塞队列 | ReentrantLock |
PriorityBlockingQueue |
支持优先级的阻塞队列 | ReentrantLock |
SynchronousQueue |
不存储元素的阻塞队列 | 直接传递 |
LinkedBlockingDeque |
双端阻塞队列 | 可伸缩的链表实现 |
ConcurrentHashMap
ConcurrentHashMap
是 HashMap
的线程安全版本,适用于高并发读写场景。与 Collections.synchronizedMap()
不同的是,它不是对整个 Map 加锁,而是使用分段锁(Segment)(JDK 7)或 CAS + synchronized(JDK 8+)实现细粒度控制,从而提高并发性能。
putIfAbsent
、replace
、computeIfPresent
等。ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("a", 1);
map.putIfAbsent("b", 2);
map.forEach((k, v) -> System.out.println(k + "=" + v));
CopyOnWriteArrayList
CopyOnWriteArrayList
是一个线程安全的 List
实现,适用于读多写少的场景。其核心思想是写时复制(Copy-on-Write),即在写操作时复制一份新的数组,完成写操作后再替换旧数组,从而避免加锁。
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
list.forEach(System.out::println);
ConcurrentLinkedQueue
ConcurrentLinkedQueue
是一个无界的线程安全非阻塞队列,基于链表实现。它使用 CAS(Compare and Swap)操作来保证线程安全,适用于高并发场景下的生产者-消费者模型。
take()
)。ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
queue.offer("1");
queue.offer("2");
String item = queue.poll(); // 出队
System.out.println("出队元素: " + item);
BlockingQueue
系列BlockingQueue
是一个支持阻塞操作的队列接口,当队列为空时,获取元素的操作会阻塞;当队列满时,插入元素的操作会阻塞。常见的实现类有:
ArrayBlockingQueue
:基于数组的有界队列。LinkedBlockingQueue
:基于链表的可配置有界或无界队列。PriorityBlockingQueue
:支持优先级排序的无界队列。SynchronousQueue
:不存储元素的队列,用于直接传递。BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
// 生产者线程
new Thread(() -> {
for (int i = 0; i < 100; i++) {
try {
queue.put(i);
System.out.println("生产: " + i);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}).start();
// 消费者线程
new Thread(() -> {
while (true) {
try {
Integer value = queue.take();
System.out.println("消费: " + value);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}).start();
ConcurrentSkipListMap
与 ConcurrentSkipListSet
这两个集合类分别实现了线程安全的有序 Map 和 Set,底层基于跳表(SkipList)实现,支持并发读写操作。
ConcurrentSkipListMap<String, Integer> map = new ConcurrentSkipListMap<>();
map.put("c", 3);
map.put("a", 1);
map.put("b", 2);
map.forEach((k, v) -> System.out.println(k + "=" + v));
ConcurrentSkipListSet<String> set = new ConcurrentSkipListSet<>();
set.add("apple");
set.add("banana");
set.forEach(System.out::println);
集合类 | 读性能 | 写性能 | 是否有序 | 是否支持 null | 适用场景 |
---|---|---|---|---|---|
ConcurrentHashMap |
极高 | 高 | 否 | 否 | 高并发键值对存储 |
CopyOnWriteArrayList |
极高 | 低 | 否 | 是 | 读多写少的 List |
ConcurrentLinkedQueue |
高 | 高 | 否 | 否 | 非阻塞队列 |
ArrayBlockingQueue |
中 | 中 | 否 | 否 | 有界阻塞队列 |
LinkedBlockingQueue |
高 | 高 | 否 | 否 | 无界/有界阻塞队列 |
ConcurrentSkipListMap |
高 | 中 | 是 | 否 | 有序 Map |
ConcurrentSkipListSet |
高 | 中 | 是 | 否 | 有序 Set |
根据不同的业务场景和性能需求,选择合适的并发集合类非常重要:
ConcurrentHashMap
CopyOnWriteArrayList
ConcurrentLinkedQueue
ArrayBlockingQueue
LinkedBlockingQueue
ConcurrentSkipListMap
或 ConcurrentSkipListSet
Collections.synchronizedXXX
:虽然也能实现线程安全,但性能远不如 java.util.concurrent
中的类。CopyOnWriteArrayList
中频繁写入:写操作会复制整个数组,频繁写入会导致性能下降。ConcurrentHashMap
和 CopyOnWriteArrayList
在并发修改时可能读到旧数据。ConcurrentHashMap
的原子方法:如 putIfAbsent
、computeIfPresent
,避免手动加锁。Java 提供的并发集合类极大地简化了多线程环境下集合的使用,它们通过高效的并发控制机制(如分段锁、CAS、写时复制等)实现了良好的线程安全性和性能表现。
合理选择并发集合不仅能提升程序的并发能力,还能避免死锁、竞态条件等常见问题。希望本文能帮助你深入理解并发集合的原理与使用方式,并在实际项目中灵活运用。