线程安全类:CopyOnWriteArrayList是如何保证线程安全的?

线程安全一直是编程方面最为注意的一个点,今天就来聊聊关于线程安全类CopyOnWriteArrayList是如何实现线程安全的。
关于保证线程安全,最常规的做法也就是加锁,但是加锁势必会导致性能方面的下降,这是无法避免的,那么有没有什么好的办法在尽可能保证性能的情况下加锁呢,接下来看看CopyOnWriteArrayList源码。
通过源码发现CopyOnWriteArrayList底层也是通过一个数组保存数据的:

/** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;

在写入操作时,加了一把互斥锁ReentrantLock以保证线程安全

public boolean add(E e) {
		//获取锁
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lock();
        try {
        	//获取到当前List集合保存数据的数组
            Object[] elements = getArray();
            //获取该数组的长度(这是一个伏笔,同时len也是新数组的最后一个元素的索引值)
            int len = elements.length;
            //将当前数组拷贝一份的同时,让其长度加1
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //将加入的元素放在新数组最后一位,len不是旧数组长度吗,为什么现在用它当成新数组的最后一个元素的下标?建议自行画图推演,就很容易理解。
            newElements[len] = e;
            //替换引用,将数组的引用指向给新数组的地址
            setArray(newElements);
            return true;
        } finally {
        	//释放锁
            lock.unlock();
        }
    }

再来看看读操作:

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

总结:只在写操作做了加锁处理,但是读操作并没有做任何的加锁处理。那么问题来了,这样不就会导致线程不安全吗?答案是并不会导致线程不安全。因为读是没有加锁的,所以读是一直都能读,但是在写的时候,看到源码可以知道写入新元素时,首先会先将原来的数组拷贝一份并且让原来数组的长度+1后就得到了一个新数组,新数组里的元素和旧数组的元素一样并且长度比旧数组多一个长度,然后将新加入的元素放置都在新数组最后一个位置后,用新数组的地址替换掉老数组的地址就能得到最新的数据了。在我们执行替换地址操作之前,读取的是老数组的数据,数据是有效数据;执行替换地址操作之后,读取的是新数组的数据,同样也是有效数据,而且使用该方式能比读写都加锁要更加的效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值