CopyOnWrite机制

什么是CopyOnWrite容器

CopyOnWrite是一种在并发场景下常用的设计思想——写入时复制思想。写入时复制思想是指:当有多个调用者同时去请求一个资源数据的时候,有一个调用者需要对当前数据源进行修改,此时系统将会复制一个当前数据源的副本给调用者修改。也就是说,当我们往一个容器中添加元素的时候,不直接往容器中添加,而是将当前容器进行copy,复制出来一个新的容器,然后向新容器中添加我们需要的元素,最后将原容器的引用指向新容器。

这样我们就可以在并发的场景下对容器进行"读操作"而不需要"加锁",从而达到读写分离的目的。JDK提供了两个使用CopyOnWrite机制实现的并发容器CopyOnWriteArrayList和CopyOnWriteArraySet 。

 

CopyOnWriteArrayList

缺点

内存占用问题:因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象。如果这些对象占用的内存比较大,很有可能造成频繁的Yong GC和Full GC。

数据一致性问题:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。(当执行add或remove操作没完成时,get获取的仍然是旧数组的元素)

 

读取操作

下面的代码展示了有关读取的实现。可以发现,读取代码没有任何同步控制和所查找,因为内部数组array不会发生修改,只会被另一个array替换。

    private transient volatile Object[] array;
     public E get(int index) {
        return get(getArray(), index);
    }

    final Object[] getArray() {
        return array;
    }


      private E get(Object[] a, int index) {
        return (E) a[index];
    }

写入操作

有关写入代码如下所示。首先写入操作使用了锁,然后Object[] newElements = Arrays.copyOf(elements, len + 1) 进行内部元素的完整复制,生成一个新数组newElements。然后将新元素放入newElements。然后通过setArray(newElements);替换掉老数组。由于数组变量array是volatile类型,因此读线程可以察觉到此修改。

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

 

remove操作

由于remove操作也是“写操作,”所以也是要加锁的。remove的逻辑是将要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);
            int numMoved = len - index - 1;    //得到要删除数据的下标numMoved 
            if (numMoved == 0)     //要删除的是列表末端数据,拷贝前len - 1个数据到新数组里,
                setArray(Arrays.copyOf(elements, len - 1));
            else {    //否则,删除元素之外的其他元素拷贝到新数组里
                Object[] newElements = new Object[len - 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                setArray(newElements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }

 


参考资料

《实战Java高并发程序设计》

 

 

 

 

你可能感兴趣的:(多线程)