CopyOnWriteArrayList 是 Java 并发包 (java.util.concurrent
) 中基于 写时复制(Copy-On-Write) 机制实现的线程安全列表,专为 读多写少 的高并发场景设计。下面从核心原理、用途、用法、示例、优缺点及适用场景进行详细解析。
🔧 一、基本原理
📌 1. 核心机制:写时复制(Copy-On-Write)
- 写操作流程 :
- 加锁:使用
ReentrantLock
保证写操作的原子性。 - 复制数组:复制当前底层数组(
volatile Object[] array
)生成新数组。 - 修改新数组:在新数组上执行增删改操作。
- 替换引用:将底层数组引用指向新数组(
setArray(newArray)
)。 - 释放锁。
- 加锁:使用
- 读操作:直接访问当前数组(无锁),性能极高。
🔍 2. 数据可见性与一致性
-
volatile
数组引用:确保写操作完成后,新数组对所有读线程立即可见。 - 弱一致性:读操作可能访问旧数组快照,无法实时感知最新修改(迭代器基于创建时的快照遍历)。
🛠️ 二、核心特性与适用场景
⚡ 1. 核心优势
- 无锁读:读操作(
get
、size
、迭代)无需同步,性能远超synchronizedList
或Vector
。 - 线程安全:写操作通过复制机制避免数据竞争。
- 迭代安全:迭代器遍历期间,即使其他线程修改列表,也不会抛出
ConcurrentModificationException
。
🎯 2. 典型适用场景
场景 | 说明 |
---|---|
事件监听器列表 | 监听器注册后极少修改,但事件触发频繁读取(如 GUI 事件系统)。 |
配置管理 | 配置加载后极少更新,但高频读取(如白名单、黑名单)。 |
缓存快照 | 缓存数据的只读视图,允许短暂的数据延迟。 |
💡 关键条件:数据读取频率远高于写入频率(例如读写比例 ≥ 10:1)。
💻 三、代码示例与用法
📝 1. 基本操作
import java.util.concurrent.CopyOnWriteArrayList;
public class COWExample {
public static void main(String[] args) {
// 初始化
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
// 添加元素(写操作触发复制)
list.add("Java");
list.add("Python");
// 读取元素(无锁)
System.out.println(list.get(0)); // 输出 "Java"
// 迭代器基于快照遍历
Iterator<String> it = list.iterator();
list.add("C++"); // 修改列表
while (it.hasNext()) {
System.out.println(it.next()); // 只输出 "Java", "Python"
}
}
}
🔄 2. 多线程安全示例
CopyOnWriteArrayList<Integer> dataCache = new CopyOnWriteArrayList<>();
// 读线程(可并发执行)
Runnable reader = () -> {
for (int num : dataCache) {
System.out.println("Read: " + num);
}
};
// 写线程(低频触发)
Runnable writer = () -> {
dataCache.add(newData); // 写操作复制数组
};
// 启动多个读线程和少量写线程
ExecutorService pool = Executors.newCachedThreadPool();
pool.execute(reader);
pool.execute(writer);
⚖️ 四、优缺点分析
维度 | 优点 | 缺点 |
---|---|---|
性能 | 读操作无锁,性能极高。 | 写操作需复制数组,时间复杂度 O(n)。 |
内存 | - | 写操作导致内存占用翻倍,可能触发 GC 压力。 |
一致性 | 避免 ConcurrentModificationException 。 | 弱一致性:读操作可能读取旧数据。 |
适用性 | 完美契合读多写少场景。 | 写频繁场景性能急剧下降,不适用大数据量。 |
🔁 五、替代方案与最佳实践
🔄 1. 替代方案选择
场景 | 推荐替代 |
---|---|
写多读少 | ConcurrentLinkedQueue 。 |
读写均衡且强一致性 | Collections.synchronizedList() 。 |
键值对结构 | ConcurrentHashMap 。 |
🛡️ 2. 最佳实践
- 批量写入:用
addAll()
替代多次add()
,减少数组复制次数。 - 控制数据量:避免存储超大型列表(如 > 1万元素),防止写操作卡顿。
- 避免迭代中修改:迭代器不支持
remove()
/set()
,调用会抛UnsupportedOperationException
。
💎 总结
- 核心价值:以空间换时间,为高并发读场景提供无锁线程安全支持。
- 使用原则:严格限于 读多写少 且允许弱一致性的场景(如配置管理、事件监听)。
- 避坑指南:写频繁或大数据量场景禁用,优先考虑
ConcurrentHashMap
或同步列表。
通过合理应用
CopyOnWriteArrayList
,可在保证线程安全的同时最大化读性能,成为高并发系统的利器 🔥。建议结合源码(如add()
和iterator()
方法)深入理解其实现细节。