CopyOnWriteArrayList
深度解析(Java 并发集合)
1. 核心设计思想
- 写时复制 (Copy-On-Write):
- 读操作:直接访问当前数组,无锁无阻塞
- 写操作:加锁 → 复制新数组 → 修改新数组 → 替换原数组引用(
volatile
写) - 数据隔离:读写操作在不同数组上进行,实现完全并发
2. 底层实现
// JDK 17+ 源码关键字段
public class CopyOnWriteArrayList<E> {
private transient volatile Object[] array; // volatile 保证可见性
final transient ReentrantLock lock = new ReentrantLock(); // 写锁
// 读操作示例(无锁)
public E get(int index) {
return (E) getArray()[index];
}
// 写操作示例(加锁复制)
public boolean add(E e) {
lock.lock(); // 加锁(Java 17 前用 synchronized)
try {
Object[] es = getArray();
int len = es.length;
es = Arrays.copyOf(es, len + 1); // 复制新数组
es[len] = e;
setArray(es); // volatile 写,原子替换
return true;
} finally {
lock.unlock();
}
}
}
3. 关键特性详解
特性 | 实现机制 |
---|---|
线程安全读 | 基于 volatile 数组引用 + 不变性(读操作不修改原始数组) |
弱一致性迭代器 | 迭代器保存创建时的数组快照(final Object[] snapshot ) |
写操作代价 | O(n) 时间复杂度(需复制整个数组) |
内存占用 | 写操作期间同时存在两个完整数组(旧数组+新数组) |
锁粒度 | 写操作使用全局锁(Java 17 前:synchronized ;Java 17+:ReentrantLock ) |
4. 性能特征
graph LR
A[操作类型] --> B[读 get/size/contains]
A --> C[写 add/remove]
B --> D[吞吐量:极高<br>接近无锁性能]
C --> E[吞吐量:极低<br>随集合大小线性下降]
5. 适用场景 vs 不适用场景
适用场景 | 不适用场景 |
---|---|
事件监听器列表 | 实时交易系统订单队列 |
只读为主的配置数据 | 高频更新的计数器 |
黑名单/白名单(低频更新) | 大型对象集合(>10,000元素) |
需要避免 ConcurrentModificationException | 要求强一致性的迭代操作 |
6. 经典陷阱与解决方案
陷阱 1:循环写入导致内存爆炸
// 错误示例:每次循环触发全量复制
List<Integer> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 100_000; i++) {
list.add(i); // 复制 100_000 次数组!
}
// 正确方案:批量添加
list.addAll(IntStream.range(0, 100_000).boxed().toList());
陷阱 2:误用迭代器修改
CopyOnWriteArrayList<String> list = ...;
Iterator<String> it = list.iterator();
while (it.hasNext()) {
it.remove(); // 抛出 UnsupportedOperationException
}
// 正确方案:直接操作原集合
list.remove("item");
7. 与相似容器对比
特性 | CopyOnWriteArrayList | Collections.synchronizedList | ConcurrentLinkedQueue |
---|---|---|---|
读性能 | ⭐⭐⭐⭐⭐ (无锁) | ⭐⭐ (全局锁) | ⭐⭐⭐⭐ (CAS) |
写性能 | ⭐ (复制成本) | ⭐⭐ (全局锁) | ⭐⭐⭐⭐⭐ (无锁) |
迭代器一致性 | 弱一致(快照) | 快速失败(fail-fast) | 弱一致 |
内存开销 | 高(双数组) | 低 | 中(节点对象) |
适用场景 | 读多写少(>1000:1) | 写多读少 | 队列操作 |
8. 实战最佳实践
-
批量写入优化
// 合并多次写操作 void addItems(List<Item> items) { lock.lock(); // 自定义锁控制批量操作 try { Object[] newElements = Arrays.copyOf(getArray(), array.length + items.size()); System.arraycopy(items.toArray(), 0, newElements, array.length, items.size()); setArray(newElements); } finally { lock.unlock(); } }
-
内存敏感场景优化
// 使用弱引用减少大对象内存占用 CopyOnWriteArrayList<WeakReference<BigObject>> list = ...; // 定期清理失效引用 list.removeIf(ref -> ref.get() == null);
-
监控关键指标
- 写操作频率(超过 100次/秒 需考虑替代方案)
- GC 压力(观察
G1 Young Generation
是否频繁触发)
9. 底层优化演进
- Java 9:引入
ArraysSupport
加速数组复制 - Java 17:锁机制从
synchronized
改为ReentrantLock
(提升竞争性能) - Java 21:虚拟线程优化(减少写操作线程阻塞成本)
10. 替代方案参考
- 读多写少:
CopyOnWriteArrayList
(最佳选择) - 写多读少:
ConcurrentLinkedDeque
+synchronized
迭代 - 读写均衡:
ReentrantReadWriteLock
+ArrayList
- 超大集合:
ConcurrentHashMap
模拟分段列表
设计哲学启示:
CopyOnWriteArrayList
是典型的 空间换时间+读写分离 思想的实现,其价值不在于通用性,而在于特定场景(监听器、配置存储等)提供无锁读的极致性能。理解其设计边界比掌握 API 更重要。