在Java中,线程安全的集合类主要分为两类:传统线程安全集合和现代并发集合。以下是常见的线程安全集合及其特点:
1. 传统线程安全集合
这些集合通过 synchronized 关键字或全局锁实现线程安全,但性能较低,适合简单场景。
(1) Vector
- 特点:
- 动态数组实现,继承自
AbstractList。 - 所有方法(如
add、get、remove)都通过synchronized加锁。 - 致命缺陷:全局锁导致并发性能差(比
CopyOnWriteArrayList慢10倍以上),且迭代器未自动同步,可能抛出ConcurrentModificationException。
- 动态数组实现,继承自
- 适用场景:已淘汰,不推荐使用。
(2) Hashtable
- 特点:
- 基于哈希表的键值对存储。
- 所有方法(如
put、get)都通过synchronized加锁。 - 不允许
null键或值。
- 适用场景:已淘汰,推荐使用
ConcurrentHashMap替代。
(3) Stack
- 特点:
- 继承自
Vector,实现后进先出(LIFO)的栈结构。 - 所有方法通过
synchronized加锁。
- 继承自
- 适用场景:已淘汰,推荐使用
Deque接口的线程安全实现(如ConcurrentLinkedDeque)。
(4) Properties
- 特点:
- 继承自
Hashtable,用于存储键值对(通常为字符串)。 - 所有方法通过
synchronized加锁。
- 继承自
- 适用场景:适合简单的配置管理,但性能较低。
2. 现代并发集合(java.util.concurrent 包)
这些集合通过更高效的锁机制(如分段锁、CAS)或无锁算法实现线程安全,适合高并发场景。
(1) ConcurrentHashMap
- 特点:
- 基于哈希表的键值对存储,支持高并发。
- JDK 7:采用分段锁(Segment Lock)机制,将哈希表分为多个段,每个段独立加锁。
- JDK 8+:使用 CAS(Compare and Swap)和
synchronized实现更细粒度的锁。 - 支持迭代时修改,避免
ConcurrentModificationException。 - 不允许
null键或值。
- 适用场景:高频读写场景(如缓存、计数器)。
(2) CopyOnWriteArrayList
- 特点:
- 动态数组实现,适合读多写少的场景。
- 写时复制:修改操作(如
add、remove)会复制整个数组,在副本上修改。 - 读操作无锁,性能接近
ArrayList。 - 写操作开销大(每次修改需复制数组)。
- 适用场景:监控系统、配置管理(如遍历频繁但修改较少)。
(3) CopyOnWriteArraySet
- 特点:
- 基于
CopyOnWriteArrayList的线程安全集合。 - 底层依赖
CopyOnWriteArrayList的写时复制机制。 - 不允许
null元素。
- 基于
- 适用场景:读多写少的集合场景。
(4) ConcurrentLinkedQueue
- 特点:
- 基于链表的无界非阻塞队列。
- 使用 CAS(无锁算法)实现线程安全。
- 支持高并发的生产者-消费者模式。
- 适用场景:高吞吐量的队列操作(如事件驱动系统)。
(5) BlockingQueue 接口及实现类
- 常见实现类:
- ArrayBlockingQueue:基于数组的有界阻塞队列。
- LinkedBlockingQueue:基于链表的无界/有界阻塞队列。
- PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
- SynchronousQueue:不存储元素的阻塞队列(直接传递)。
- 特点:
- 阻塞操作:队列满时
put阻塞,队列空时take阻塞。 - 适合生产者-消费者模式。
- 阻塞操作:队列满时
- 适用场景:任务调度、线程池、消息队列。
(6) ConcurrentSkipListMap & ConcurrentSkipListSet
- 特点:
- 基于跳表(SkipList)实现的线程安全有序集合。
- 支持并发读写,且保持元素有序。
ConcurrentSkipListMap是有序的键值对存储,ConcurrentSkipListSet是无重复元素的有序集合。
- 适用场景:需要有序且线程安全的键值对或集合操作。
(7) Collections.synchronizedXXX 包装类
- 特点:
- 通过
Collections.synchronizedList、Collections.synchronizedMap等方法包装普通集合(如ArrayList、HashMap)。 - 所有方法通过
synchronized加锁。 - 注意:迭代时需手动同步(
synchronized(list))。
- 通过
- 适用场景:兼容旧代码,但性能较低。
3. 其他线程安全集合
- ConcurrentLinkedDeque:基于链表的无界非阻塞双端队列,支持并发读写。
- ArrayBlockingQueue:基于数组的有界阻塞队列,适合固定容量的生产者-消费者场景。
- LinkedBlockingDeque:基于链表的无界/有界阻塞双端队列。
4. 选择线程安全集合的原则
| 场景 | 推荐集合 | 原因 |
|---|---|---|
| 读多写少 | CopyOnWriteArrayList | 写时复制,读操作无锁,性能高。 |
| 高频读写 | ConcurrentHashMap | 分段锁或 CAS 实现,支持高并发。 |
| 生产者-消费者模式 | BlockingQueue(如 ArrayBlockingQueue) | 阻塞操作简化线程协调。 |
| 有序集合 | ConcurrentSkipListSet | 跳表实现,线程安全且有序。 |
| 无需线程安全 | 普通集合(如 ArrayList) | 但需通过 synchronized 或 Collections.synchronizedXXX 手动同步。 |
5. 注意事项
- 避免过度使用
Vector和Hashtable:它们的全局锁性能较差,已被现代集合取代。 - 权衡性能与一致性:例如
CopyOnWriteArrayList写操作开销大,适合读多写少场景。 - 迭代器同步:使用
Collections.synchronizedXXX包装的集合时,迭代必须手动同步。
6. 示例代码
// 使用 ConcurrentHashMap
Map<String, String> map = new ConcurrentHashMap<>();
map.put("key1", "value1");
String value = map.get("key1");
// 使用 CopyOnWriteArrayList
List<String> list = new CopyOnWriteArrayList<>();
list.add("item1");
for (String item : list) {
System.out.println(item);
}
// 使用 BlockingQueue
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
queue.put("task1"); // 阻塞直到队列有空位
String task = queue.take(); // 阻塞直到队列有元素
通过合理选择线程安全集合,可以显著提升多线程程序的性能和可靠性!
THE END


