Java集合线程同步需依场景选工具:同步包装类适合读多写少但需手动同步迭代和复合操作;并发集合类如ConcurrentHashMap、CopyOnWriteArrayList等采用分段锁、CAS等机制提升性能;手动同步应使用私有final锁对象并控制粒度;不可变集合适用于构建后不修改的只读数据,天然线程安全。

Java中集合的线程同步不是靠“加锁”就完事,关键在于选对工具、用对场景。直接在非线程安全集合(如 ArrayList、HashMap)上手动加 synchronized 块,容易出错且性能差;而盲目使用同步包装类又可能掩盖并发问题。真正有效的方案,是结合数据结构特性、访问模式和一致性要求来选择。
同步包装类:简单但有陷阱
通过 Collections.synchronizedXxx() 可将普通集合转为线程安全版本,例如:
Collections.synchronizedList(new ArrayList()) 或 Collections.synchronizedMap(new HashMap())。
这类包装类内部对每个方法加了 synchronized(锁的是整个对象),适合读多写少、操作粒度粗的场景。但要注意两点:
立即学习“Java免费学习笔记(深入)”;
- 迭代必须手动同步,否则仍可能抛
ConcurrentModificationException。例如遍历时要写成:synchronized (syncList) { for (Object o : syncList) { ... } } - 复合操作(如“检查是否存在,再添加”)不是原子的,需额外同步块包裹,否则存在竞态条件。
并发集合类:推荐的现代方案
JDK 5 引入的 java.util.concurrent 包提供了真正为高并发设计的集合,它们不依赖全局锁,而是采用分段锁、CAS、不可变性等机制提升吞吐量。
-
ConcurrentHashMap:支持高并发读写,读操作完全无锁,写操作只锁对应桶(JDK 8 后改为 CAS + synchronized 锁单个 Node)。适用于缓存、计数器等高频读写场景。 -
CopyOnWriteArrayList:写时复制,读操作不加锁、无阻塞,适合读远多于写的场景(如监听器列表)。但写操作开销大,且迭代器看到的是快照,无法反映实时变更。 -
ConcurrentLinkedQueue/BlockingQueue实现(如ArrayBlockingQueue、LinkedBlockingQueue):适用于生产者-消费者模型,前者无锁、后者基于锁+条件队列,按是否需要阻塞行为选择。
手动同步:可控但需谨慎
当标准同步集合无法满足定制需求(比如需要多个集合协同更新、或复杂不变式校验),可自行封装并统一管理锁对象。
- 避免使用
this或集合实例本身作为锁,防止外部干扰;建议定义私有 final 锁对象:private final Object lock = new Object(); - 所有涉及共享状态的操作(包括读、写、判断、修改)都必须用同一把锁保护,确保原子性和可见性。
- 注意锁粒度:锁太粗(如整个方法)影响并发度;锁太细(如每个元素一把锁)增加复杂度和死锁风险。
不可变集合:零同步的终极方案
如果集合构建后不再修改,用不可变集合(如 ImmutableList、ImmutableMap 来自 Guava,或 JDK 10+ 的 List.of()、Map.of())是最安全高效的选择。
- 天然线程安全:没有可变状态,无需同步,无内存可见性问题。
- 适用于配置项、枚举映射、静态查找表等只读数据。
- 注意:不可变 ≠ 不可变引用。若将不可变集合赋值给非 final 字段,仍需保证发布安全(如用 final 修饰字段,或通过安全构造函数发布)。










