优先使用并发集合类(如concurrenthashmap、copyonwritearraylist、blockingqueue等),其次慎用同步包装类,必要时显式加锁(synchronized或reentrantlock),最优策略是从源头减少共享(不可变集合、threadlocal、消息传递)。

直接用非线程安全集合(比如 ArrayList、HashMap)在多线程下操作,很容易出错——数据丢失、size不准、抛 ConcurrentModificationException,甚至死循环。关键不是“能不能用”,而是“怎么用才不出问题”。下面几种方式按推荐优先级讲清楚。
优先用并发集合类
java.util.concurrent 包里的集合是专为并发设计的,内部做了细粒度锁或无锁优化,性能和安全性兼顾。
- ConcurrentHashMap:替代 HashMap。支持高并发读写,put/get 基本不用额外加锁;复合操作如 computeIfAbsent、merge 等也自带原子性。
- CopyOnWriteArrayList:适合读远多于写的场景(比如监听器列表)。每次 add/remove 都复制数组,读操作完全无锁,但写开销大,别用在高频修改场景。
- BlockingQueue(如 LinkedBlockingQueue、ArrayBlockingQueue):生产者-消费者模型首选。自动处理线程阻塞与唤醒,offer/poll/take/put 都线程安全。
- ConcurrentLinkedQueue / ConcurrentSkipListMap:无锁队列或有序并发 Map,适用于特定排序或高性能队列需求。
慎用同步包装类
Collections.synchronizedList(new ArrayList()) 这类包装能快速让普通集合“看起来”线程安全,但有明显陷阱:
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 单个方法(如 add、get)是同步的,但遍历时必须手动加锁,否则迭代中被其他线程修改就会抛异常。
- 复合操作(如“先检查再添加”)不是原子的,两个线程可能同时通过 if 判断,然后都执行 add,导致重复数据。
- 所有操作串行排队,高并发下性能差,相当于把并行变串行。
需要时才显式加锁
当必须用普通集合,或者并发集合无法满足原子逻辑(比如跨多个集合协调操作),就用显式同步:
- 用 synchronized 块包裹关键代码段,锁对象建议用集合本身或专用 lock 对象,避免锁太粗影响并发度。
- 更灵活的场景可用 ReentrantLock,支持可中断、超时、公平锁等特性,但记得配 try-finally 保证 unlock。
- 注意:锁范围要覆盖所有相关操作,包括读、写、判断、修改,漏掉一个环节就可能出问题。
从源头减少共享风险
比“怎么锁更安全”,更好的思路是“尽量不共享”:
- 用 不可变集合(如 List.of()、Collections.unmodifiableList),一旦创建就不变,天然线程安全。
- 用 ThreadLocal 给每个线程分配独立副本,避免竞争,适合缓存、上下文数据等场景。
- 改用消息传递(如通过 BlockingQueue 传递数据),让线程间不直接操作同一集合,而是通过队列交换状态。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










