Java集合框架:并发集合专题之CopyOnWriteArrayList的写时复制机制与适用场景(18)

写时复制的艺术:CopyOnWriteArrayList深度解析



从生活中的复印机到Java并发容器

想象一下办公室里的共享文件场景:当多位同事需要同时查阅和修改同一份文档时,传统的做法是大家轮流等待使用原件,这显然效率低下。而现代办公室通常会采用另一种方案——每当有人需要修改时,就复印一份副本,在副本上修改,最后再将修改好的版本设为新的"原件"。这种"写时复制"的思路正是CopyOnWriteArrayList的设计哲学。

在Java并发编程的世界里,List接口的线程安全实现一直是个挑战。传统的Vector虽然线程安全,但它的同步机制过于粗暴——就像给整个文档库上了锁,每次只允许一个人操作。而Collections.synchronizedList()包装器也好不到哪去,虽然灵活些,但本质上还是"全锁"策略。CopyOnWriteArrayList则另辟蹊径,它采用了我们开头提到的复印机策略:读操作完全无锁,可以并发进行;写操作则通过复制整个底层数组来实现线程安全。

这种设计带来了惊人的读取性能,特别适合"读多写少"的场景。就像公司里查阅规章制度的人远多于修订制度的人一样,很多实际业务场景也遵循这个规律。但值得注意的是,它并非万能药,不当使用反而会导致性能问题。接下来,我们将深入剖析这个与众不同的并发容器,了解它的内部机制、适用场景以及使用时的注意事项。

写时复制机制原理解析

CopyOnWriteArrayList的核心秘密藏在它的名字里——“写时复制”(Copy-On-Write)。这个机制就像我们平时编辑重要文档时的版本控制策略:你永远不会直接修改原始文件,而是先创建一个副本,所有的修改都在副本上进行,确认无误后再替换原始文件。这种保守策略虽然会消耗一些额外的空间和时间来创建副本,但却换来了读取操作的无锁并发能力。

深入源码,我们可以看到这个类内部维护了一个volatile修饰的数组引用:

// CopyOnWriteArrayList的核心存储结构
private transient volatile Object[] array;

volatile关键字确保了数组引用的内存可见性,这是实现无锁读取的关键。当需要修改列表时(如add、set、remove等操作),实现步骤非常规范:

  1. 获取当前数组引用
  2. 创建新数组(长度根据需要调整)
  3. 将原数组内容拷贝到新数组
  4. 在新数组上执行修改
  5. 将内部array引用指向新数组

这个过程通过显式锁(ReentrantLock)保证原子性,下面是add方法的简化版实现:

public boolean add(E e) {
   
    final ReentrantLock lock = this.lock;
    lock.lock();  // 加锁确保写操作的独占性
    try {
   
        Object[] elements = getArray();  // 获取当前数组
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);  // 创建副本
        newElements[len] = e;  // 在副本上修改
        setArray(newElements);  // 切换引用
        return true;
    } finally {
   
        lock.unlock();  // 释放锁
    }
}

读取操作则完全不需要同步,因为array引用是volatile的,且每次读取都会拿到一个确定的数组快照:

public E get(int index) {
   
    return get(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

双囍菜菜

你的鼓励是我创作最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值