CopyOnWriteArrayList 深度解析

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. 与相似容器对比
特性CopyOnWriteArrayListCollections.synchronizedListConcurrentLinkedQueue
读性能⭐⭐⭐⭐⭐ (无锁)⭐⭐ (全局锁)⭐⭐⭐⭐ (CAS)
写性能⭐ (复制成本)⭐⭐ (全局锁)⭐⭐⭐⭐⭐ (无锁)
迭代器一致性弱一致(快照)快速失败(fail-fast)弱一致
内存开销高(双数组)中(节点对象)
适用场景读多写少(>1000:1)写多读少队列操作
8. 实战最佳实践
  1. 批量写入优化

    // 合并多次写操作
    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();
        }
    }
    
  2. 内存敏感场景优化

    // 使用弱引用减少大对象内存占用
    CopyOnWriteArrayList<WeakReference<BigObject>> list = ...;
    
    // 定期清理失效引用
    list.removeIf(ref -> ref.get() == null);
    
  3. 监控关键指标

    • 写操作频率(超过 100次/秒 需考虑替代方案)
    • GC 压力(观察 G1 Young Generation 是否频繁触发)
9. 底层优化演进
  • Java 9:引入 ArraysSupport 加速数组复制
  • Java 17:锁机制从 synchronized 改为 ReentrantLock(提升竞争性能)
  • Java 21:虚拟线程优化(减少写操作线程阻塞成本)
10. 替代方案参考
  • 读多写少CopyOnWriteArrayList(最佳选择)
  • 写多读少ConcurrentLinkedDeque + synchronized 迭代
  • 读写均衡ReentrantReadWriteLock + ArrayList
  • 超大集合ConcurrentHashMap 模拟分段列表

设计哲学启示
CopyOnWriteArrayList 是典型的 空间换时间+读写分离 思想的实现,其价值不在于通用性,而在于特定场景(监听器、配置存储等)提供无锁读的极致性能。理解其设计边界比掌握 API 更重要。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值