arraylist非线程安全是设计取舍,为单线程高性能牺牲并发安全;add非原子、扩容不安全、modcount快速失败机制共同导致线程问题;collections.synchronizedlist通过同步代理提供强一致性,但迭代需手动加锁。

ArrayList 本身没有内置同步机制,所有方法(如 add、remove、get)都不加锁,多个线程同时读写时,会出现数据覆盖、size 错误、数组越界或 ConcurrentModificationException 等问题。
为什么非线程安全?关键在三处
不是“没写好”,而是设计取舍:牺牲并发安全性,换取单线程下的高性能。
- add 操作非原子:看似一行代码,实际分三步——读 size → 写 elementData[size] → 更新 size。多线程可能交错执行,导致两个线程写进同一个下标,后写覆盖前写
-
扩容过程不安全:当数组满时,
grow()会新建数组、复制元素、替换引用。若两个线程同时触发扩容,可能一个线程的复制结果被另一个覆盖,造成数据丢失 -
modCount 快速失败机制:迭代器依赖
modCount判断是否被修改。多线程中一个线程改了集合,另一个线程还在遍历,立刻抛ConcurrentModificationException——这不是线程安全保护,而是报错提醒你出错了
Collections.synchronizedList 是最常用的包装方式
它不改变 ArrayList 本身,而是返回一个“同步代理”,把所有 public 方法用 synchronized 包裹起来:
List
- 优点:兼容性强,可包装任意
List实现;语义清晰,强一致性保障 - 注意:迭代时必须手动加锁,否则仍可能出错
正确迭代写法:
synchronized (safeList) {
Iterator
while (it.hasNext()) {
System.out.println(it.next());
}
}
其他包装/替代方案对比
-
Vector:老式线程安全类,方法自带
synchronized,但粒度粗(整个对象一把锁),性能低,已不推荐新项目使用 - CopyOnWriteArrayList:适合读多写少场景(如监听器列表)。写操作加锁+复制整个数组,读完全无锁。但内存开销大、写延迟高,不适合高频增删
- 显式加锁(ReentrantLock):更灵活,可控制锁范围和策略,但需自行管理,容易出错,一般只在特殊性能调优时采用











