CopyOnWriteArrayList通过写时复制实现读操作无锁、写操作加锁复制:读直接访问volatile数组快照,写则加ReentrantLock、复制数组并volatile更新引用,适用于读多写少场景。

CopyOnWriteArrayList 是 Java 并发包(java.util.concurrent)中专为“读多写少”场景设计的线程安全列表。它的核心机制是:**读操作完全无锁,写操作通过复制底层数组实现线程安全**——不依赖 synchronized 或 CAS 锁,而是用“写时复制(Copy-On-Write)”策略规避并发冲突。
为什么读操作能完全不加锁?
因为每次读操作(如 get()、size()、iterator())都直接访问当前持有的数组引用,而该数组在读期间绝不会被修改。底层数组是 volatile 修饰的,保证了可见性;且所有读方法内部没有同步块或锁,因此高并发读不会阻塞、不会竞争。
注意:迭代器返回的是创建时刻的快照,后续其他线程的增删改对当前迭代器不可见(即弱一致性),但这是设计取舍,换来了零锁开销。
写操作如何做到线程安全?
每次写(add()、set()、remove() 等)都会:
在调试 Java 后端 API 故障时使用此技能,尤其适用于以下任务:分析日志、跨 Controller、Service 和 Mapper 层追踪请求、定位异常根因、检查 SQL 执行或参数流转,以及生成简洁的诊断结论与修复方案。
- 先获取独占锁(内部使用
ReentrantLock,不是 synchronized) - 复制当前数组,对副本进行修改
- 用
volatile写将新数组引用原子地更新到底层字段 - 释放锁
由于写操作互斥执行,且新数组发布通过 volatile 保证可见性,所以多个线程写不会导致数据错乱。但要注意:写操作性能开销较大(尤其数组大时),不适合高频写场景。
典型适用场景与使用建议
适合监听器列表、配置项缓存、状态白名单等——读远多于写、允许短暂最终一致性、不能容忍读阻塞。
- 避免在循环中反复调用
size()+get(i)做随机读(虽无锁,但每次get()都有边界检查和数组访问,不如直接用增强 for) - 不要依赖迭代器的实时性;若需强一致性遍历,应自行加锁或改用其他并发集合
- 写操作尽量批量(如用
addAll()替代多次add()),减少复制次数 - 不适用于存储大量元素(如百万级),否则每次写复制成本过高
一个简洁示例
// 安全地在多线程中读写监听器列表
CopyOnWriteArrayList<runnable> listeners = new CopyOnWriteArrayList();
// 读 —— 完全无锁,可并发执行
public void notifyAll() {
for (Runnable r : listeners) { // 使用快照迭代器
r.run();
}
}
// 写 —— 线程安全,但会复制数组
public void addListener(Runnable r) {
listeners.add(r); // 内部加锁 + 复制 + volatile 写
}</runnable>
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










