collections.synchronizedlist仅对单个方法加锁,复合操作如“size()后get(0)”仍线程不安全,易抛indexoutofboundsexception;迭代必须显式同步,否则可能concurrentmodificationexception;适合单次无状态操作,非原子逻辑应选copyonwritearraylist或reentrantlock等替代方案。

为什么 Collections.synchronizedList 不能直接当线程安全列表用
它只是给底层 List 的每个方法加了 synchronized,但**复合操作依然不安全**。比如先 size() 再 get(0),中间可能被其他线程删掉元素,直接抛 IndexOutOfBoundsException。
常见错误现象:ConcurrentModificationException 不常出现(因为没用迭代器),但逻辑错乱很隐蔽——比如“检查非空后取首元素”失败、“遍历中删除”漏处理。
- 使用场景:仅适合单次调用、无状态依赖的操作,如
add()、get(i)(且 i 确保有效) - 不要用于需要原子性保证的逻辑,例如 “如果列表为空则添加默认项”
- 底层锁是
this,即 list 实例本身;多个线程若持有不同 synchronizedList 包装同一底层数组,仍会出问题
替代方案选哪个:CopyOnWriteArrayList 还是 synchronizedList
CopyOnWriteArrayList 适合读多写少、迭代频繁的场景;Collections.synchronizedList 适合写稍多、但不需要复合操作原子性的简单封装。
性能差异明显:CopyOnWriteArrayList 每次写都复制数组,写操作 O(n),内存开销大;synchronizedList 写是 O(1),但所有方法串行执行,高并发下吞吐低。
立即学习“Java免费学习笔记(深入)”;
- 迭代时:
CopyOnWriteArrayList迭代器不抛ConcurrentModificationException,且看到的是快照;synchronizedList的迭代器必须手动同步,否则可能报错 - 兼容性:两者都实现
List接口,但CopyOnWriteArrayList不支持set(null)等部分操作(会 NPE),而synchronizedList行为和原 list 一致 - 别混用:不要把
CopyOnWriteArrayList传给Collections.synchronizedList——锁冗余,还可能掩盖问题
怎么正确用 synchronizedList 避免迭代出错
它的迭代器不是线程安全的,必须显式同步外部容器,否则在多线程遍历时大概率触发 ConcurrentModificationException 或跳过/重复元素。
实操建议:
- 迭代前必须用原始包装对象加锁:
synchronized (list) { for (Object o : list) { ... } } - 不要用增强 for 循环却不加锁——那等于裸奔
- 如果要用
Iterator,也要在同步块内获取:Iterator it = list.iterator();,且后续next()/hasNext()都在同一个同步块里 - 注意:锁对象是
list实例,不是list.iterator()返回的对象
什么时候该放弃封装,直接上 ReentrantLock 或 ConcurrentLinkedQueue
当你发现要反复写 synchronized (list) { ... },或者需要条件等待(如“等列表非空再取”)、批量操作原子性(如“移除所有匹配项”),说明 synchronizedList 的粒度太粗,已经不适合了。
更合适的选择:
- 需要精确控制锁范围或尝试获取锁:用
ReentrantLock+ 普通ArrayList - 只做生产/消费类操作(add/take/poll):优先考虑
ConcurrentLinkedQueue或BlockingQueue实现 - 需要 map-keyed 并发访问:直接用
ConcurrentHashMap,别试图用synchronizedList存 key-value 对 - 别为了“看起来线程安全”而套两层:比如
Collections.synchronizedList(new CopyOnWriteArrayList())—— 锁冲突且无意义
真正难的不是加锁,而是判断哪些操作必须原子、哪些可以松耦合。一旦开始补 synchronized 块,就该重新评估数据结构选型了。










