CopyOnWriteArrayList分析

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CopyOnWriteArrayList分析相关的知识,希望对你有一定的参考价值。

  CopyOnWriteArrayList是一个在多线程操作中线程安全的ArrayList的一个变种,她在所有对ArrayList对象的编辑操作(add,set等)都会复制一份副本,因此无论是对ArrayList操作还是对其iterator操作都不会抛ConcurrentModificationException异常。

使用场景

  CopyOnWriteArrayList通常适用于读多写少的场景,对每次写操作都会复制一份数据的副本,因此不会影响原先数据的读操作。虽然每次复制副本会耗费时间,但相对于使用synchronize来保证线程安全,在特定场景下效果还是不错的。

源码分析

    /** The lock protecting all mutators */
    final transient ReentrantLock lock = new ReentrantLock();

    /** 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;
    }

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

  CopyOnWriteArrayList维护一个ReentrantLock锁,主要用于保证同一时间只能有一个线程对array数据进行复制编辑操作(set,add等),避免多线程下对数据复制操作造成数据不一致现象。 同时array声明为volatile,保证线程读取数据时将内存的数据刷新至缓存,从而得到最新数据。写入数据时保证最新数据写入到内存。在CopyOnWriteArrayList所有操作中,获取数据和写入数据不是直接使用this.array=array,而是使用getArray()和setArray()操作,刷新一下缓存,保证获得的是最新数据及将最新数据写入内存。

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

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

  CopyOnWriteArrayList所有的读操作都是线程安全的,因为每次读操作读的都是元数据的一个snapshot。同时由getArray()来保证读到的数据都是最新版本的。

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#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();
        }
    }

    /**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).  Returns the element that was removed from the list.
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    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();
        }
    }

  再看一下编辑操作,这里以add()和remove()为例,可以看到每次只能有一个线程可以对ArrayList进行修改,而在修改之前通过getArray()来保证获取最新的array数据,然后复制一份array的副本newElements(复制过程根据具体操作而定,例如add会全部复制并预留一个位置,而remove则部分复制),对副本进行编辑后再通过setArray将最新的副本赋给array。这样其他线程再进行读操作时通过getArray()获取的就是最近编辑过的数据。

COWIterator

  传统的ListIterator在遍历列表时如果列表被修改会有fast-fail机制来保证线程安全,而于CopyOnWriteArrayList已经具备线程安全性,并且CopyOnWriteArrayList的Iterator更多的用于数据的遍历,所以其关闭了iterator对数据的编辑功能.

/**
         * Not supported. Always throws UnsupportedOperationException.
         * @throws UnsupportedOperationException always; {@code remove}
         *         is not supported by this iterator.
         */
        public void remove() {
            throw new UnsupportedOperationException();
        }

 

    /**
     * Not supported. Always throws UnsupportedOperationException.
     * @throws UnsupportedOperationException always; {@code set}
     *         is not supported by this iterator.
     */
    public void set(E e) {
        throw new UnsupportedOperationException();
    }

    /**
     * Not supported. Always throws UnsupportedOperationException.
     * @throws UnsupportedOperationException always; {@code add}
     *         is not supported by this iterator.
     */
    public void add(E e) {
        throw new UnsupportedOperationException();
    }

 

总结

  • CopyOnWriteArrayList通过编辑操作复制元数据副本的方式成功避免了多线程操作List线程不安全的问题,同时通过声明array[]为volitile类型保证线程每次读取的数据都是最新数据。
  • 由于每次编辑操作都会复制一份副本,因此CopyOnWriteArrayList只适用于读多改少的场景,在其他场景中还是建议使用synchronize或者Collections.SynchronizedList来保证线程安全
  • CopyOnWriteArrayList不能保证线程遍历的数据一定是最新的数据,因此是能适用于实时性要求不高的场景。

以上是关于CopyOnWriteArrayList分析的主要内容,如果未能解决你的问题,请参考以下文章

Java并发编程之CopyOnWriteArrayList源码分析

Java多线程系列--“JUC集合”02之 CopyOnWriteArrayList

CopyOnWriteArrayList实现原理及源码分析

「源码分析」CopyOnWriteArrayList 中的隐藏知识,你Get了吗?

CopyOnWriteArrayList分析——能解决什么问题

死磕 java集合之CopyOnWriteArrayList源码分析