Java容器源码重点回顾——CopyOnWriteArrayList

1. CopyOnWriteArrayList概述

之前介绍过ArrayList,但是我们知道ArrayList是线程不安全的。如果多个线程同时写数据,就会抛出ConcurrentModificationException。然后我们又学过Vector,它的实现方式是在方法中都加入synchronized关键字,但是这会导致效率低下,并且只有原子方法下能够保证线程安全。

后面,我们又学习了锁,比较常用的是ReentrantLock。为了适用读多写少的场景,出现了ReentrantReadWriteLock。虽然相比于ReentrantLock,ReentrantReadWriteLock虽然在一定程度上提高了读写性能,比较适合读多写少的场景,但是在有线程持有写锁的时候,读线程还是会被阻塞

因此,CopyOnWriteArrayList就是针对了读写场景下互斥的缺陷,进行优化。它在每次需要进行修改的时候,先拷贝一份原来的数组,在拷贝的新数组上进行修改,最后再将引用指向新数组。这样就能保证,有线程在修改时,读线程不会被阻塞。提高了读写的性能。

但是,它也有缺陷

  1. 每次更新都要拷贝原来的数组,如果原数组比较大,就会占用2倍的空间;
  2. 无法做到实时一致性,只能做到最终一致性。因为在新数组上进行修改时,读线程是没法感知的。只有更新了引用的指向之后,才能获取到最新值。

2. 成员变量

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    // 控制写操作线程安全的锁
    final transient ReentrantLock lock = new ReentrantLock();

    // 用来存储数据的数组
    private transient volatile Object[] array;
}

CopyOnWriteArrayList的成员变量很简单,只有两个。

  • lock: 这个成员变量是用来保证写线程的线程安全的,要保证在同一个时刻,不能有多个线程修改数组;
  • array:这个成员变量是用来存储数据的。需要特别注意的是它使用volatile进行修饰,这个会在后面进行讲解。

3. get方法

    public E get(int index) {
        return get(getArray(), index);
    }

    final Object[] getArray() {
        return array;
    }

    private E get(Object[] a, int index) {
        return (E) a[index];
    }

CopyOnWriteArrayList的get方法我们可以看到很简单,也不需要加锁什么的,就是简单地从数组读取返回,因为写线程并不会操作原来的数组。

4. 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();  // 解锁
        }
    }

在add方法中,我们可以看到首先会使用ReentrantLock进行加锁,这是为了保证写线程的线程安全,避免多个写线程复制多次数组。然后就在新数组上进行修改,修改完成后再将指针指向新数组。add方法流程如下图:

在这里插入图片描述

需要注意的是:之前讲过array数组是使用volatile修饰的,根据volatile的happens-before规则,写线程对于数组的引用修改是对于读线程可见的。 这里的volatile只能保证引用修改的可见性,不能保证数组元素修改的可见性。所以读线程并不能立马感知到修改,可能会隔好几秒才能感知到。

参考文章:并发容器之CopyOnWriteArrayList

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值