在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