CopyOnWriteArrayList源码分析

76 阅读4分钟

构造函数

public CopyOnWriteArrayList() {
    setArray(new Object[0]);
}

final void setArray(Object[] a) {
    array = a;
}

add(E e);

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); //拷贝数组,新数组长度+1
        newElements[len] = e;  //新数组插入新元素
        setArray(newElements); //更新旧数组
        return true;
    } finally {
        lock.unlock();
    }
}

addIfAbsent(元素存在则不添加)

public boolean addIfAbsent(E e) {
    Object[] snapshot = getArray(); //快照
    return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
        addIfAbsent(e, snapshot);
}

这里检查元素是否已经存在

private static int indexOf(Object o, Object[] elements,
                           int index, int fence) {
    if (o == null) { //如果传入元素为null
        for (int i = index; i < fence; i++)
            if (elements[i] == null) //数组中已有null,相当于重复,元素已存在
                return i;
    } else { //不为null
        for (int i = index; i < fence; i++)
            if (o.equals(elements[i])) //判断到元素已存在
                return i; 
    }
    return -1; //元素不存在,进入addIfAbsent(e, snapshot)

}

核心方法

private boolean addIfAbsent(E e, Object[] snapshot) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] current = getArray();
        int len = current.length;
        if (snapshot != current) {
            // Optimize for lost race to another addXXX operation
            int common = Math.min(snapshot.length, len);
            for (int i = 0; i < common; i++)
                //当前数组的元素与当前快照的元素不相等 且 e与当前元素相等
                if (current[i] != snapshot[i] && eq(e, current[i]))
                // 表示在snapshot与current之间修改了数组,并且设置了数组某一元素为e,已经存在
                // 比如另一个线程修改了某个索引处的元素为e,此时新元素e想插入发现已经存在,返回false
                    return false;
            // (common-1,len-1)范围判断元素是否重复
            // 可以想象另一个线程新插入一个元素e,在索引len-1处,这时本线程应该感知后续索引有没有重复
            if (indexOf(e, current, common, len) >= 0)
                    return false;
        }
        //拷贝一份数组,长度+1
        Object[] newElements = Arrays.copyOf(current, len + 1);
        //插入新元素
        newElements[len] = e;
        //更新原数组
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

迭代器

    /**
     * Copy-On-Write 迭代器,用于遍历不可变列表。
     *
     * <p>该迭代器提供了对不可变列表的快照的访问,并且不允许执行修改操作。
     * 迭代器在内部维护了一个指向列表快照的游标,通过 hasNext()、next()、hasPrevious()、previous() 等方法
     * 进行遍历,但不支持对列表进行元素的增、删、改操作。
     *
     * @param <E> 列表元素的类型
     */
    static final class COWIterator<E> implements ListIterator<E> {
        /** Snapshot of the array */
        private final Object[] snapshot;  // 快照
        /** Index of element to be returned by subsequent call to next.  */
        private int cursor;  // 游标

        /**
         * 构造 COWIterator 对象。
         *
         * @param elements 列表的快照
         * @param initialCursor 初始游标位置
         */
        private COWIterator(Object[] elements, int initialCursor) {
            cursor = initialCursor;
            snapshot = elements;
        }

        /**
         * 判断是否有下一个元素。
         *
         * @return 如果有下一个元素,返回 true;否则返回 false
         */
        public boolean hasNext() {
            return cursor < snapshot.length;
        }

        /**
         * 判断是否有上一个元素。
         *
         * @return 如果有上一个元素,返回 true;否则返回 false
         */
        public boolean hasPrevious() {
            return cursor > 0;
        }

        /**
         * 返回下一个元素。
         *
         * @return 下一个元素
         * @throws NoSuchElementException 如果没有下一个元素
         */
        @SuppressWarnings("unchecked")
        public E next() {
            if (!hasNext())
                throw new NoSuchElementException();
            return (E) snapshot[cursor++];
        }

        /**
         * 返回上一个元素。
         *
         * @return 上一个元素
         * @throws NoSuchElementException 如果没有上一个元素
         */
        @SuppressWarnings("unchecked")
        public E previous() {
            if (!hasPrevious())
                throw new NoSuchElementException();
            return (E) snapshot[--cursor];
        }

        /**
         * 返回下一个元素的索引。
         *
         * @return 下一个元素的索引
         */
        public int nextIndex() {
            return cursor;
        }

        /**
         * 返回上一个元素的索引。
         *
         * @return 上一个元素的索引
         */
        public int previousIndex() {
            return cursor - 1;
        }

        /**
         * 不支持的操作,始终抛出 UnsupportedOperationException。
         *
         * @throws UnsupportedOperationException 始终抛出
         */
        public void remove() {
            throw new UnsupportedOperationException();
        }

        /**
         * 不支持的操作,始终抛出 UnsupportedOperationException。
         *
         * @param e 要设置的元素
         * @throws UnsupportedOperationException 始终抛出
         */
        public void set(E e) {
            throw new UnsupportedOperationException();
        }

        /**
         * 不支持的操作,始终抛出 UnsupportedOperationException。
         *
         * @param e 要添加的元素
         * @throws UnsupportedOperationException 始终抛出
         */
        public void add(E e) {
            throw new UnsupportedOperationException();
        }

        /**
         * 对列表中剩余的元素执行给定的操作。
         *
         * @param action 对元素执行的操作
         * @throws NullPointerException 如果操作为 null
         */
        @Override
        public void forEachRemaining(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            Object[] elements = snapshot;
            final int size = elements.length;
            for (int i = cursor; i < size; i++) {
                @SuppressWarnings("unchecked") E e = (E) elements[i];
                action.accept(e);
            }
            cursor = size;
        }
    }

CopyOnWriteArrayList的迭代器保证数据是当时状态是因为执行构造函数时保存了当时的数组引用

private COWIterator(Object[] elements, int initialCursor) {
            cursor = initialCursor;
            snapshot = elements; //快照
        }

COWIterator表示迭代器,其也有一个Object类型的数组作为CopyOnWriteArrayList数组的快照,这种快照风格的迭代器方法在创建迭代器时使用了对当时数组状态的引用。此数组在迭代器的生存期内不会更改,因此不可能发生冲突,并且迭代器保证不会抛出 ConcurrentModificationException。创建迭代器以后,迭代器就不会反映列表的添加、移除或者更改。在迭代器上进行的元素更改操作(remove、set 和 add)不受支持。这些方法将抛出 UnsupportedOperationException。

别忘了CopyOnWriteArrayList添加或删除元素都是依赖CopyOf创新新的数组,然后再设置到成员变量array上

int[] oldArr = {1,2,3};
int[] newArr = Arrays.copyOf(oldArr,oldArr.length);
System.out.println(oldArr == newArr); //false

image.png

这也是CopyOnWriteArrayList的缺陷,在写操作时因为频繁copyOf会消耗内存,如果数组较大可能导致gc,所以CopyOnWriteArrayList更适合读多写少的场景。

此外,它对实时性没有保证,只能保证数据的最终一致性