在Java中如何选择线程安全的集合_Java并发集合选型说明

Java线程安全集合选型需按场景:读多写少用CopyOnWriteArrayList,高并发读写用ConcurrentHashMap,强一致性用BlockingQueue,简单共享用AtomicInteger等原子类。

Java中选择线程安全的集合,关键不是“有没有锁”,而是看场景:读多写少、写频繁、需要强一致性、还是允许弱一致性?不同集合的设计目标差异很大,选错不仅性能差,还可能出并发bug。

读多写少

场景:优先用 CopyOnWriteArrayListCopyOnWriteArraySet

适合迭代远多于增删的场景,比如监听器列表、配置白名单。它的原理是每次写操作都复制整个数组,读完全无锁、高性能,但写操作开销大、内存占用高。

  • 迭代时不会抛 ConcurrentModificationException,也不需要加锁
  • 不适用于高频写(如每秒数百次 add/remove),否则 GC 压力明显
  • 不支持在迭代过程中修改(虽然不报错,但修改的是旧副本,新副本不可见)

高并发读写均衡:首选 ConcurrentHashMap

这是最常用的线程安全 Map,JDK 8 后基于 CAS + synchronized 分段锁优化,读操作几乎无锁,写操作只锁对应 bin 链表或红黑树头节点。

  • 不支持 containsKey + put 这类复合操作的原子性,需用 computeIfAbsentmerge
  • 遍历时用 entrySet().forEach(...) 是弱一致性快照,可能看不到最新写入,也不会阻塞写入
  • 避免用 size() 判断是否为空——它返回估算值;要用 isEmpty()(它是准确的)

需要强一致性和阻塞行为:考虑 BlockingQueue 系列

比如 ArrayBlockingQueue(有界、可重入锁)、LinkedBlockingQueue(默认无界、读写双锁)、PriorityBlockingQueue(带排序的无界队列)。它们专为生产者-消费者模型设计,支持阻塞插入/获取、超时等待、容量控制。

  • 有界队列(如 ArrayBlockingQueue)能防止内存溢出,适合资源受限系统
  • 不要在循环里反复调用 poll() 空转,应使用 take() 或带 timeout 的 poll(long, TimeUnit)
  • SynchronousQueue 不存储元素,每个 put 必须配一个 take,适合任务交接场景(如 newCachedThreadPool 内部使用)

简单计数或状态共享:别造轮子,直接用 AtomicIntegerAtomicReference 等原子类

如果只是做计数器、开关标志、单个对象引用更新,比用 ConcurrentHashMap 或包装成 synchronized 方法更轻量、更高效。

  • AtomicIntegerincrementAndGet()compareAndSet() 是无锁且原子的
  • AtomicReference 可用于替换整个集合引用(注意内部 List 本身仍需线程安全)
  • 复杂逻辑若涉及多个变量协同更新,原子类不够用,应考虑 StampedLock 或显式锁

不复杂但容易忽略:没有“万能线程安全集合”,只有“更适合当前访问模式”的集合。先理清读写比例、是否需要阻塞、一致性要求、内存敏感度,再对照特性选型,比盲目套用 synchronized 包装或一律上 ConcurrentHashMap 更可靠。