【多线程和并发】CopyOnWriteArrayList的实现原理

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/Soldier49Zed/article/details/102647413

CopyOnWrite容器写时复制的容器,也就是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器(改变引用的指向)。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写在不同的容器上进行,注意,写的时候需要加锁。

1)以下代码是向CopyOnWriteArrayList中add方法的实现,可以发现在添加的时候是需要加锁的,否则多线程写的时候会Copy出N个副本出来。

public boolean add(E e) {
    final ReentrantLock lock = this.lock; //加的是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();
    }
}

在CopyOnWriteArrayList里处理写操作(包括add、remove、set等)是先将原始的数据通过Arrays.copyof()来生成一份新的数组,然后在新的数据对象上进行写,写完后再将原来的引用指向到当前这个数据对象,这样保证了每次写都是在新的对象上。然后读的时候就是在引用的当前对象上进行读(包括get、iterator等),不存在加锁和阻塞。

CopyOnWriteArrayList中写操作需要大面积复制数组,所以性能肯定很差,但是读操作因为操作的对象和写操作不是同一个对象,读之间也不需要加锁,读和写之间的同步处理只是在写完后通过一个简单的 = 将引用指向新的数组对象上来,这个几乎不需要时间,这样读操作就很快很安全,适合在多线程里使用。

2)读的时候不需要加锁,如果读的时候有线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据(在原容器中进行读)。

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

CopyOnWriteArrayList在读上效率很高,由于写的手每次都要将原数组复制到一个新数组中,所以写的效率不高。

CopyOnWriteArrayList容器有很多优点,但是也存在两个问题。即内存占用问题数据一致性问题

  1. 内存占用问题。因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会有同时驻扎两个对象的内存,旧的对象和新写入的对象。针对内存占用问题,可以
    1. 通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制。
    2. 不使用CopyOnWrite容器,而是用其他的并发容器,如ConcurrentHashMap。
  2. 数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果希望写入的数据能被马上读到,就不能使用CopyOnWrite容器。

猜你喜欢

转载自blog.csdn.net/Soldier49Zed/article/details/102647413