CopyOnWriteArrayList特性及部分源码解读(doge)

CopyOnWriteArrayList解决了ArrayList并发安全性问题,其特性是在修改结构时(remove/add/clear等)会使用ReentrantLock加锁,创建一个新数组来操作,然后对老数组进行合并覆盖(根据具体方法来看),而在读取元素时,始终读取的是原数组的值,实现了共享读,排斥写的效果。

CopyOnWriteArrayList的get方法:

 	private transient volatile Object[] array;

	final Object[] getArray() {
        return array; //array是实际存储元素的数组
    }
	
	private E get(Object[] a, int index) {
        return (E) a[index]; //返回数组a索引位index的元素
    }

    public E get(int index) {
        return get(getArray(), index); //返回array数组索引位index的元素
    }

可以看到,get方法并没有加锁,因此在对集合进行get操作时始终读的是array的元素,而且没有加锁。

CopyOnWriteArrayList的add方法:

	final transient ReentrantLock lock = new ReentrantLock();

	final void setArray(Object[] a) {
        array = a;
    }
	
	public boolean add(E e) {
        final ReentrantLock lock = this.lock; //获取锁
        lock.lock(); //加锁
        try {
            Object[] elements = getArray(); //通过getArray()获取老数组
            int len = elements.length; //拿到老数组的长度
            //使用Arrays.copyOf方法基于老数组创建新数组,长度为老数组的长度+1
            Object[] newElements = Arrays.copyOf(elements, len + 1); 
            newElements[len] = e; //直接将新元素添加到新数组的末尾
            setArray(newElements); //将老数组覆盖
            return true;
        } finally {
            lock.unlock(); //锁的正确释放
        }
    }

CopyOnWriteArrayList的remove方法:

	public E remove(int index) { //删除对应索引的元素
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray(); //获得老数组
            int len = elements.length; //得到老数组的长度
            E oldValue = get(elements, index); //拿到老数组index索引的元素
            //计算需要移动的元素个数
            int numMoved = len - index - 1; 
            if (numMoved == 0)
                //若需要移动的元素个数为0,则说明删除的是最后一个元素,因此只需要保留len-1位元素
                setArray(Arrays.copyOf(elements, len - 1));
            else {
                //若需要移动的元素个数不为0,说明删除的元素不是最后一个,因此需要分开copy
                //首先创建一个新数组newElements,长度为老数组长度-1
                Object[] newElements = new Object[len - 1];
                //先将老数组在index之前的所有元素copy到新数组
                System.arraycopy(elements, 0, newElements, 0, index);
                //再将老数组在index之后的所有元素copy到新数组
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                setArray(newElements);
            }
            return oldValue; //返回删除的元素
        } finally {
            lock.unlock();
        }
    }

add方法与remove方法都使底层存放元素的数组array保持在一个理想的容量,即有多少个元素,数组的长度就有多长,因此我们在阅读源码时也可以发现,CopyOnWriteArrayList并没有像ArrayList一样提供类似以下初始化集合容量的构造方法:

	public ArrayList(int initialCapacity) { //ArrayList初始化容量构造方法
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

并且也没有类似以下的扩容方法:

	private void grow(int minCapacity) { //ArrayList gorw方法
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

CopyOnWriteArrayList在保证了并发安全的同时,也带来了较大的开销,因为在每一次变动时,都需要基于原数组copy一份新数组出来,这无疑是非常昂贵的,因此在修改操作频繁时,非常不建议使用该类,但若是修改操作不频繁并且读取操作频繁时,使用该类还是很划算的。总之具体是否该使用该类来作为线程安全的集合使用,需要结合实际的应用场景来看。

最后感谢观看,若有不到位或错误的地方,还请大佬们在评论区中指出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值