美文网首页
CopyOnWriteArrayList 源码

CopyOnWriteArrayList 源码

作者: 陆阳226 | 来源:发表于2020-03-17 17:51 被阅读0次

    基本原理

    CopyONWriteArrayList是ArrayList的线程安全版本,顾名思义在发生write操作时copy数组:write操作会加锁,新建一个数组将旧数组中的内容复制到新数组。read操作没有锁,可能write操作已经创建了新数组,但read操作还在读旧数组,所以read会缺乏一些实时性。

    方法API

    write操作中使用的锁对象,使用的是synchronized关键字,注释中说明在ReentrantLock和builtin monitors两者都能完成任务时,作者更倾向于使用builtin monitors。

    /**
     * The lock protecting all mutators.  (We have a mild preference
     * for builtin monitors over ReentrantLock when either will do.)
     */
    final transient Object lock = new Object();
    

    使用的数组存储内容,数组值通过get、set方法操作,方法是包访问权限是为了同一个包里的CopyOnWriteArraySet类

    /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;
    
    /**
     * Gets the array.  Non-private so as to also be accessible
     * from CopyOnWriteArraySet class.
     */
    final Object[] getArray() {
        return array;
    }
    
    final void setArray(Object[] a) {
        array = a;
    }
    

    get操作没有加锁只需要在简单的读取即可

    public E get(int index) {
        return elementAt(getArray(), index);
    }
    
    static <E> E elementAt(Object[] a, int index) {
        return (E) a[index];
    }
    

    在添加元素的操作和基于index的删除操作中,就是简单的加锁然后一系列数组的复制等操作,得到新的数组然后setArray,都是单线程操作。

    public boolean add(E e) {
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            es = Arrays.copyOf(es, len + 1);
            es[len] = e;
            setArray(es);
            return true;
        }
    }
    

    这样的一系列api有,都是简单的加锁然后数组复制移动的操作

    public E set(int index, E element)
    public boolean add(E e)
    public void add(int index, E element)
    public E remove(int index)
    void removeRange(int fromIndex, int toIndex)
    public boolean addAll(Collection<? extends E> c)
    public boolean addAll(int index, Collection<? extends E> c)
    

    在基于对象的删除操作中,需要遍历数组找到这个对象的index,遍历比较对象消耗较大,如果全部加锁会很影响,所以这里在遍历时没有加锁,在查找到index后基于index进行remove操作。
    当在snapshot数组中查找时,有可能其他线程做了write操作,此时的current数组会跟snapshot不一致,具体有几种情况

    • current跟snapshot一致,跳过if代码块,执行数组的复制操作
    • current跟snapshot不一致,此时在if代码块中再次查找index,在index前后都有可能出现对象o
      • 在index之前是否出现了对象o,即for循环中的内容,如果找到o即确定新的index,没找到才会执行后面的if
      • 第一个if,index >= len 说明在for已经遍历了整个数组没找到,返回false
      • 第二个if,current[index] == o 判断index处的对象是否是o,是就index没变
      • 如果上面三个操作都没找到o,在current数组中从index开始遍历查找o

    在index之前查找时使用了current[i] != snapshot[i]的短路操作比直接在整个current数组里indexOfRange要效率一些。

    public boolean remove(Object o) {
        Object[] snapshot = getArray();
        int index = indexOfRange(o, snapshot, 0, snapshot.length);
        return index >= 0 && remove(o, snapshot, index);
    }
    
    private boolean remove(Object o, Object[] snapshot, int index) {
        synchronized (lock) {
            Object[] current = getArray();
            int len = current.length;
            if (snapshot != current) findIndex: {
                int prefix = Math.min(index, len);
                for (int i = 0; i < prefix; i++) {
                    // 如果current[i] != snapshot[i]说明current[i]肯定不等于o
                    // 直接短路不需要执行equals判断
                    if (current[i] != snapshot[i]
                        && Objects.equals(o, current[i])) {
                        index = i;
                        break findIndex;
                    }
                }
                if (index >= len)
                    return false;
                if (current[index] == o)
                    break findIndex;
                index = indexOfRange(o, current, index, len);
                if (index < 0)
                    return false;
            }
            Object[] newElements = new Object[len - 1];
            System.arraycopy(current, 0, newElements, 0, index);
            System.arraycopy(current, index + 1,
                             newElements, index,
                             len - index - 1);
            setArray(newElements);
            return true;
        }
    }
    

    基于对象判断的addIfAbsent方法也使用了snapshot来遍历然后加锁对比current来add

    public boolean addIfAbsent(E e) {
            Object[] snapshot = getArray();
            return indexOfRange(e, snapshot, 0, snapshot.length) < 0
                && addIfAbsent(e, snapshot);
        }
    
    private boolean addIfAbsent(E e, Object[] snapshot) {
        synchronized (lock) {
            Object[] current = getArray();
            int len = current.length;
            if (snapshot != current) {
                int common = Math.min(snapshot.length, len);
                for (int i = 0; i < common; i++)
                    // 如果找到 return false 添加失败
                    if (current[i] != snapshot[i]
                        && Objects.equals(e, current[i]))
                        return false;
                if (indexOfRange(e, current, common, len) >= 0)
                        return false;
            }
            // 没找到执行数组的复制操作
            Object[] newElements = Arrays.copyOf(current, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        }
    }
    

    CopyOnWriteArrayList iterator迭代

    COWIterator是基于snapshot的,不支持write相关的操作,会抛出UnsupportedOperationException。所以迭代会出现实时性的问题,迭代期间对CopyOnWriteArrayList的修改都不会被体现出来。

    /** Snapshot of the array */
    private final Object[] snapshot;
    /** Index of element to be returned by subsequent call to next.  */
    private int cursor;
    
    COWIterator(Object[] es, int initialCursor) {
        cursor = initialCursor;
        snapshot = es;
    }
    

    相关文章

      网友评论

          本文标题:CopyOnWriteArrayList 源码

          本文链接:https://www.haomeiwen.com/subject/jdqxyhtx.html