collections.synchronizedlist仅保证单操作原子性,复合操作需手动加锁;copyonwritearraylist适合读多写少场景;concurrenthashmap迭代器弱一致;自定义集合应使用私有锁对象而非synchronized方法。

直接用 Collections.synchronizedList 等包装器不够安全
很多人以为调用 Collections.synchronizedList(new ArrayList()) 就万事大吉,其实只是给单个操作加了锁。遍历、条件判断等复合操作仍可能出错——比如 if (!list.isEmpty()) list.remove(0);,两步之间其他线程可能已修改集合,导致 IndexOutOfBoundsException 或逻辑错误。
实操建议:
- 对简单读写场景(如仅做
add/get),包装器可满足基本同步需求 - 涉及迭代、批量操作或“检查-执行”逻辑时,必须手动加锁,用
synchronized (list) { ... }包裹整个临界区 - 注意:包装器返回的集合,其
iterator()方法返回的迭代器**不是线程安全的**,不能在多线程中边遍历边修改
CopyOnWriteArrayList 适合读多写少,但别滥用
它通过每次写操作复制整个数组实现线程安全,读操作完全无锁。但代价明显:内存占用高、写性能差、迭代器看到的是快照,无法反映后续写入。
适用场景:
立即学习“Java免费学习笔记(深入)”;
- 监听器列表(如事件回调注册表)——添加/删除极少,遍历极频繁
- 配置项缓存、白名单等静态或低频变更数据
不适用场景:
- 元素数量大(>1000)、写操作频繁(如每秒多次
add) - 需要强一致性视图(如要求迭代器实时反映最新状态)
- 使用
list.size()做循环终止条件,因写操作期间 size 可能已变,但迭代器看不到变化,易漏处理
ConcurrentHashMap 是 Map 的首选,但 keySet() 和 values() 不是实时视图
它采用分段锁(Java 8+ 改为 CAS + synchronized + 红黑树),并发读写性能远超 Hashtable 或 Collections.synchronizedMap。但要注意其“弱一致性”设计:
-
keySet()、values()、entrySet()返回的集合**不支持结构修改**(调用remove()会抛UnsupportedOperationException) - 这些集合的迭代器是弱一致性的:不会抛
ConcurrentModificationException,但可能跳过刚插入的元素,也可能重复返回同一元素 - 若需原子性批量操作(如“如果 key 不存在则 put”),优先用
computeIfAbsent、merge等内置方法,而非先containsKey再put
自定义集合类加锁时,别只锁方法,要锁对象实例
常见错误是给 public synchronized void add(E e) 加 synchronized,看似线程安全,但若多个线程持有不同包装实例(如两个 synchronizedList 包装同一个底层 ArrayList),锁对象不同,照样并发冲突。
更稳妥的做法:
- 显式使用私有 final 锁对象:
private final Object lock = new Object();,所有临界区都synchronized (lock) - 避免锁
this或公开对象(如集合字段),防止外部代码意外参与锁竞争 - 若集合被多个组件共享,考虑将同步逻辑上移到业务层统一控制,比在集合内部加锁更可控
线程安全从来不是“套个壳就完事”,关键在明确操作边界、识别复合动作、匹配锁粒度——尤其当集合被跨模块传递时,谁负责同步、锁在哪一层,往往比用哪个类更重要。










