面试题:Java 线程安全的集合有哪些?

在Java中,线程安全的集合类主要分为两类:传统线程安全集合现代并发集合。以下是常见的线程安全集合及其特点:


1. 传统线程安全集合

这些集合通过 synchronized 关键字或全局锁实现线程安全,但性能较低,适合简单场景。

(1) Vector

  • 特点
    • 动态数组实现,继承自 AbstractList
    • 所有方法(如 addgetremove)都通过 synchronized 加锁。
    • 致命缺陷:全局锁导致并发性能差(比 CopyOnWriteArrayList 慢10倍以上),且迭代器未自动同步,可能抛出 ConcurrentModificationException
  • 适用场景:已淘汰,不推荐使用。

(2) Hashtable

  • 特点
    • 基于哈希表的键值对存储。
    • 所有方法(如 putget)都通过 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

  • 特点
    • 动态数组实现,适合读多写少的场景。
    • 写时复制:修改操作(如 addremove)会复制整个数组,在副本上修改。
    • 读操作无锁,性能接近 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.synchronizedListCollections.synchronizedMap 等方法包装普通集合(如 ArrayListHashMap)。
    • 所有方法通过 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
喜欢就支持一下吧
点赞10 分享