CopyOnWriteArrayList源码学习(jdk8)

CopyOnWriteArrayList是一个线程安全的集合类,通过ReentrantLock实现加锁。其在添加、删除元素时复制新数组以确保线程安全,读操作无需加锁,提高了并发性能。然而,这种设计可能导致数据一致性问题,因为读操作可能在写操作完成前读取到旧数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CopyOnWriteArrayList是一个线程安全的集合类,它的底层也是数组,和ArrayList是一样的,但是不同是ArrayList是线程不安全,不能用于多线程中,而CopyOnWriteArrayList是线程安全的。

CopyOnWriteArrayList的属性

	/** The lock protecting all mutators */
	// 从这个可以看出CopyOnWriteArrayList的线程安全是通过ReentrantLock加锁来实现的
    final transient ReentrantLock lock = new ReentrantLock();

    /** The array, accessed only via getArray/setArray. */
    // array这个属性,就是底层的数组,这个属性加上了volatile关键字,保证了array的可见性,和有序性
    private transient volatile Object[] array;

CopyOnWriteArrayList的add()方法,添加方法大致是先复制一个新数组,将元素添加到新数组中,然后再用新数组替代旧数组。

	/**
     * 添加元素
     */
    public boolean add(E e) {
        final ReentrantLock lock = this.lock; // 得到锁
        lock.lock(); // 加锁,保证下面操作的线程安全
        try {
            Object[] elements = getArray(); // 得到旧数组
            int len = elements.length; // 旧数组长度
            /** 产生一个原数组长度加1的副本数组 */
            Object[] newElements = Arrays.copyOf(elements, len + 1); // 新数组
            newElements[len] = e; // 在新数组中进行修改操作
            setArray(newElements); // 将新数组替换旧数组
            return true;
        } finally {
            lock.unlock(); // 解锁,在finally中,保证一定释放锁
        }
    }

CopyOnWriteArrayList的get()方法,阅读源码发现,读这个操作是不需要加锁的,直接读取array数组中的数据。(get(int index)源码并没有判断下标是否会越界)

	/**
     * 读取数据操作
     */
    public E get(int index) {
    	// getArray()是得到array这个数组,直接是 array[index]
        return get(getArray(), index);
    }
	private E get(Object[] a, int index) {
        return (E) a[index];
    }

CopyOnWriteArrayList的remove()方法,删除方法是,创建一个新数组,然后将旧数组数组复制到新数组中,复制的时候,不复制要删除的元素。因此复制的时候会判断删除的元素是否是最后一个元素,如果是复制的时候直接不复制就行,如果不是,则需要很两段进行复制。(remove(int index)没有判断下标是否会越界)

	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); //得到要删除的值
            int numMoved = len - index - 1;
            if (numMoved == 0) // 判断是否是删除最后一个元素
                setArray(Arrays.copyOf(elements, len - 1)); // 直接缩短,去掉了最后一个元素
            else { // 不是删除最后一个元素
                Object[] newElements = new Object[len - 1]; // 新数组
                // System.arraycopy()是一个native方法,这个方法是用来复制数组的数据的,
                // arraycopy(被复制的数组,开始复制的下标,复制到哪个数组,从这个数组的哪个下标开始粘贴,复制的长度)
               // 以index为分界线,分两段开始复制
                System.arraycopy(elements, 0, newElements, 0, index); //复制前半段
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved); // 复制后半段
                setArray(newElements); // 新数组替代旧数组
            }
            return oldValue; // 返回被删除的值
        } finally {
            lock.unlock(); // 解锁
        }
    }

总结

CopyOnWriteArrayList在操作元素的时候,读写是分离开的,写的时候会创建一个新的数组,将旧数组元素复制到新数组中,然后将要添加的元素添加到新数组,之后新数组替代旧数组。而读的时候,直接根据下标读取元素,不会被写操作给阻塞。删除操作和写操作类似,也是创建一个新数组,但是删除需要判断,如果是删除最后一个元素,就直接复制元素,不复制最后一个就行,但是大多情况下,删除的不是最后一个元素,需要以index很分割线,分为两段进行复制到新数组中。
写操作是加锁的,读操作是不加锁的,这样读写分离可以有效的提高效率,但是这样会有一个数据一致性的问题,就是在并发情况下,一个线程,写操作添加数据,一个读操作,读取数据,可能读操作读取了旧数组的数组,导致数据不一致。(虽然array属性加入volatile关键字,但是这个写操作是先在新数组操作,然后复制到旧数组,新数组是没法对其他线程可见的。)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值