ArrayList本身非线程安全,需通过Collections.synchronizedList加锁包装、CopyOnWriteArrayList替代或手动加ReentrantLock等方式实现线程安全,且须注意复合操作原子性与底层暴露风险。

Java 中 ArrayList 本身不是线程安全的,直接在多线程环境下并发读写会引发 ConcurrentModificationException 或数据不一致问题。要实现线程安全的集合包装操作,核心思路是**加锁控制访问**或**使用线程安全的替代实现**,而不是修改 ArrayList 本身。
使用 Collections.synchronizedList 包装
这是最直接的包装方式,返回一个同步代理对象,所有方法调用都通过内部锁(mutex)串行化:
- 调用
Collections.synchronizedList(new ArrayList())得到线程安全的 List - 注意:迭代器仍需手动同步,否则可能抛出
ConcurrentModificationException - 示例:必须显式同步迭代过程
List<string> list = Collections.synchronizedList(new ArrayList());
// 安全的添加/获取
list.add("a");
String s = list.get(0);
// 迭代时需同步整个块
synchronized (list) {
for (String item : list) {
System.out.println(item);
}
}</string>
使用 CopyOnWriteArrayList 替代(适合读多写少场景)
CopyOnWriteArrayList 是 JDK 提供的线程安全 List 实现,写操作加锁并复制底层数组,读操作无锁:
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
- 适用于读操作远多于写操作、且允许读取“最终一致性”(即读到旧快照)的场景
- 写操作开销大(每次增删都复制数组),不适合高频写入
- 迭代器天然安全,不会抛
ConcurrentModificationException
手动加锁(如 ReentrantLock 或 synchronized 块)
当需要更细粒度控制(比如只对部分操作加锁,或组合多个操作为原子性)时,可自行封装:
- 用
ReentrantLock或synchronized(this)保护关键区 - 适合自定义复合操作(如“检查是否存在再添加”)
- 示例:确保 addIfAbsent 原子性
public class SafeArrayList<t> {
private final List<t> list = new ArrayList();
private final ReentrantLock lock = new ReentrantLock();
public void addIfAbsent(T item) {
lock.lock();
try {
if (!list.contains(item)) {
list.add(item);
}
} finally {
lock.unlock();
}
}
}</t></t>
避免常见误区
很多开发者误以为“包装了就万事大吉”,实际要注意:
-
Collections.synchronizedList不保证复合操作原子性(如if (!list.contains(x)) list.add(x)仍需额外同步) - 不要将底层
ArrayList暴露给外部,否则绕过包装锁导致不安全 - 优先考虑业务是否真需要 List —— 若只需唯一性,
ConcurrentHashMap的 keySet 更高效;若需排序+并发,考虑ConcurrentSkipListSet
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










