Java源码心得jdk8-CopyOnWriteArrayList和CopyOnWriteArraySet

原文日期:2017-10-18


CopyOnWrite

CopyOnWrite(简写COW)是一种写时复制技术,Java中的CopyOnWriteArrayList和CopyOnWriteArraySet两个类就使用了CopyOnWrite技术。当往集合内部数组添加修改或者删除元素时,不会直接对原数组进行操作,而是先将容器复制一份出来,然后对新的容器进行操作,最后将原容器的引用指向新容器。

更多关于写时复制技术资料可查看www.cnblogs.com/biyeymyhjob…

CopyOnWriteArrayList源码分析

由于ArrayList是线程不安全,所以,故而在多线程环境下我们一般会选择CopyOnWriteArrayList去替代ArrayList,来保证程序数据正常。CopyOnWriteArrayList实现原理主要有以下几个点。

1.在内部数组属性上增加volatile关键字修饰,以保证内部数组的可见性和原子性,但不能保证数组元素的可见性和原子性。

2.增加了一把可重入锁,在添加、修改、删除操作进行加锁处理,在读操作中没有进行加锁处理。其中写操作(添加修改删除)都不是直接在原来的数组上进行操作,而且复制一份新数组,在新数组上进行。

基于每次写操作都需要复制一份新数组,额外的增加了一部分内存消耗,并且需要进行加锁同步处理,造成每次写的代价比较大,相反读操作的代价比较小。因此它更加适用于读多写少的应用场景下,并且推荐批量写操作来代替多次写,以减少多次写操作带来的消耗。

构造函数

构造函数主要是对内部数组进行初始化,默认数组长度为0。

/**
 * Sets the array.
 */
final void setArray(Object[] a) {
    array = a;
}

/**
 * Creates an empty list.
 */
public CopyOnWriteArrayList() {
    setArray(new Object[0]);
}
复制代码

add方法

首先将旧的数组复制一份到新数组中,然后将新元素加到新数组中,最后将新数组赋值给内部数组属性,并且整个处理过程需要加锁。

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();
    }
}
复制代码

get方法

直接获取内部数组对应索引的元素,该过程没有进行加锁处理,因此它返回的元素可能不是最新的数据。

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

/**
 * {@inheritDoc}
 *
 * @throws IndexOutOfBoundsException {@inheritDoc}
 */
public E get(int index) {
    return get(getArray(), index);
}
复制代码

remove方法

该方法实现方式和add方法类似,将索引前后的元素直接拷贝到新数组中,最后将新数组赋值给内部数组属性,整个过程需要加锁处理。

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(elements, 0, newElements, 0, index);
            //复制索引后的元素到新数组中
            System.arraycopy(elements, index + 1, newElements, index,
                             numMoved);
            //内部数组属性指向新数组
            setArray(newElements);
        }
        return oldValue;
    } finally {
        lock.unlock();
    }
}
复制代码

iterator方法

该方法返回一个COWIterator迭代器实例,它遍历的是旧的内部数组,或者说遍历内部数组的一个快照,所以它迭代出来的数据可能不是最新的。如下是COWIterator迭代器的构造函数

private COWIterator(Object[] elements, int initialCursor) {
    cursor = initialCursor;
    snapshot = elements;
}
复制代码

CopyOnWriteArraySet原理

CopyOnWriteArraySet的实现借助了CopyOnWriteArrayList类,它的内部包含一个CopyOnWriteArrayList对象al,它的实现方法都是调用al对象的方法。如下是部分源代码

public CopyOnWriteArraySet() {
    al = new CopyOnWriteArrayList<E>();
}
public int size() {
    return al.size();
}
public boolean add(E e) {
        //去重,只有元素不存在,才添加
    return al.addIfAbsent(e);
}
public boolean remove(Object o) {
    return al.remove(o);
}
复制代码

Guess you like

Origin juejin.im/post/7032316999380566052