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后就得到了一个新数组,新数组里的元素和旧数组的元素一样并且长度比旧数组多一个长度,然后将新加入的元素放置都在新数组最后一个位置后,用新数组的地址替换掉老数组的地址就能得到最新的数据了。在我们执行替换地址操作之前,读取的是老数组的数据,数据是有效数据;执行替换地址操作之后,读取的是新数组的数据,同样也是有效数据,而且使用该方式能比读写都加锁要更加的效率

猜你喜欢

转载自blog.csdn.net/HolleDream/article/details/109279568