JDK源码-CopyOnWriteArrayList.java 顺带 ArrayList Vector Collections.synchronizedList(List list)

来源:互联网 发布:网络访问安全设计 编辑:程序博客网 时间:2024/05/29 06:53

0.本文目录

  • 本文目录
  • 开篇明志
  • List接口实现ArrayList
  • List接口实现Vector
  • CollectionssynchronizedListList list
  • CopyOnWriteArrayList
    • 1 CopyOnWriteArrayList 存在的问题
    • 2 CopyOnWriteArrayList小结


1.开篇明志

CopyOnWriteArrayList容器是Collections.synchronizedList(List list)的替代方案。

CopyOnWriteArrayList称为“写时复制”容器,就是在多线程操作容器对象时,把容器复制一份,这样在线程内部的修改就与其他线程无关了,而且这样设计可以做到不阻塞其他的读线程。
从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayListCopyOnWriteArraySet

另外,看看其他那些个List的实现类。


2.List接口实现——ArrayList

ArrayList是非线性安全,此类的 iteratorlistIterator方法返回的迭代器是快速失败(fast-failed)的:

在创建迭代器之后,除非通过迭代器自身的 remove 或 add 方法从结构上对列表进行修改,否则在任何时间以任何方式对列表进行修改,迭代器都会抛出 ConcurrentModificationException

在遍历列表的同时如果进行修改列表操作的话,会报ConcurrentModificationException错误。而这不是唯一的并发时容易发生的错误,在多线程进行插入操作时,由于没有进行同步操作,容易丢失数据。

    /**     * Appends the specified element to the end of this list.     *     * @param e element to be appended to this list     * @return <tt>true</tt> (as specified by {@link Collection#add})     */    public boolean add(E e) {        ensureCapacityInternal(size + 1);  // Increments modCount!!        elementData[size++] = e;  //size++ 多线程场景下数据丢失问题。        return true;    }

3.List接口实现——Vector

 * @author  Lee Boynton * @author  Jonathan Payne * @see Collection * @see LinkedList * @since   JDK1.0    */public class Vector<E>    extends AbstractList<E>    implements List<E>, RandomAccess, Cloneable, java.io.Serializable{//。。。。省略。。。。}

Vector起始于JDK1.0,它是一个线程安全的列表,采用数组实现。其线程安全的实现方式是对所有操作都加上了synchronized关键字,这种方式严重影响效率,所以,建议不再使用Vector.

Why is Java Vector class considered obsolete or deprecated?

总结: 不用Vector, 就是因为它太慢了,在每个方法前加注 synchronized . 造成独占资源。


4.Collections.synchronizedList(List list)

CopyOnWriteArrayListCollections.synchronizedList是实现线程安全的列表的两种方式。

CopyOnWriteArrayList的写操作性能较差,而多线程的读操作性能较好。

Collections.synchronizedList的写操作性能比CopyOnWriteArrayList在多线程操作的情况下要好很多,而读操作因为是采用了synchronized关键字的方式,其读操作性能并不如CopyOnWriteArrayList。

因此在不同的应用场景下,应该选择不同的多线程安全实现类。


  • Collections.synchronizedList
 /**     *  List list = Collections.synchronizedList(new ArrayList());     *      ...     *  synchronized (list) {     *      Iterator i = list.iterator(); // Must be in synchronized block     *      while (i.hasNext())     *          foo(i.next());     *  }     * <p>The returned list will be serializable if the specified list is     * serializable.     *     * @param  <T> the class of the objects in the list     * @param  list the list to be "wrapped" in a synchronized list.     * @return a synchronized view of the specified list.     */    public static <T> List<T> synchronizedList(List<T> list) {        return (list instanceof RandomAccess ?                new SynchronizedRandomAccessList<>(list) :                new SynchronizedList<>(list));//根据不同的list类型最终实现不同的包装类。    }

SynchronizedList对部分操作加上了synchronized关键字以保证线程安全。大部分的方法进行了同步。比如下面贴出的一些方法:

        public E get(int index) {            synchronized (mutex) {return list.get(index);}        }        public E set(int index, E element) {            synchronized (mutex) {return list.set(index, element);}        }        public void add(int index, E element) {            synchronized (mutex) {list.add(index, element);}        }

也有,没有同步的方法, 比如 : iterator()方法不是线程安全

        public ListIterator<E> listIterator() {            return list.listIterator(); // Must be manually synched by user 需要开发者认为的保证同步,否则仍然可能抛出 ConcurrentModificationException 异常        }        public ListIterator<E> listIterator(int index) {            return list.listIterator(index); // Must be manually synched by user 需要开发者认为的保证同步,否则仍然可能抛出 ConcurrentModificationException 异常        }

5.CopyOnWriteArrayList

  • CopyOnWriteArrayList
    先贴出两个重要的成员变量:
    /** The lock protecting all mutators 重入锁 */    final transient ReentrantLock lock = new ReentrantLock();    /** The array, accessed only via getArray/setArray. */    private transient volatile Object[] array;    //注意: array 这个变量只能通过  getArray/setArray 这两个方法进行操作

CopyOnWriteArrayList容器的实现原理:简单地说,就是在需要对容器进行操作的时候,将容器拷贝一份,对容器的修改等操作都在容器的拷贝中进行,当操作结束,再把容器容器的拷贝指向原来的容器。这样设计的好处是实现了读写分离,并且读不会发生阻塞。下面的源码是CopyOnWriteArrayList的add方法实现:

  /**     * Appends the specified element to the end of this list.     * 在数组后面添加元素     */    public boolean add(E e) {        final ReentrantLock lock = this.lock;        lock.lock();        try {            Object[] elements = getArray();            int len = elements.length;            //1.Copy出一个新数组  比 原数组长度大1            Object[] newElements = Arrays.copyOf(elements, len + 1);            //2.末尾元素赋值            newElements[len] = e;            //3.把数组指向原来的数组            setArray(newElements);            return true;        } finally {            lock.unlock();        }    }    /**     * 在index位置插入元素     */    public void add(int index, E element) {        final ReentrantLock lock = this.lock;        lock.lock();        try {            Object[] elements = getArray();            int len = elements.length;            if (index > len || index < 0)                throw new IndexOutOfBoundsException("Index: "+index+                                                    ", Size: "+len);            Object[] newElements;            int numMoved = len - index;            //1、复制出一个新的数组            if (numMoved == 0)                newElements = Arrays.copyOf(elements, len + 1);            else {                newElements = new Object[len + 1]; //数组长度加1                System.arraycopy(elements, 0, newElements, 0, index);                System.arraycopy(elements, index, newElements, index + 1,                                 numMoved);            }             //2、把新元素添加到新数组中            newElements[index] = element;             //3、把数组指向原来的数组            setArray(newElements);        } finally {            lock.unlock();        }    }

上面的三个步骤实现了写时复制的思想,在读数据的时候不会锁住list,因为写操作是在原来容器的拷贝上进行的。而且,可以看到,如果对容器拷贝修改的过程中又有新的读线程进来,那么读到的还是旧的数据。

读的代码如下:

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

CopyOnWrite并发容器用于读多写少的并发场景。


5.1. CopyOnWriteArrayList 存在的问题

存在两个问题:内存占用问题和数据一致性问题。

  • 内存占用问题
    因为需要将原来的对象进行拷贝,占用内存增加一倍。而且,在高并发的场景下,因为每个线程都拷贝一份对象在内存中,这种情况体现得更明显。由于JVM的优化机制,将会触发频繁的Young GC和Full GC,从而使整个系统的性能下降。

  • 数据一致性问题
    CopyOnWriteArrayList不能保证实时一致性,因为读线程在将引用重新指向原来的对象之前再次读到的数据是旧的,所以CopyOnWriteArrayList 只能保证最终一致性。

    因此在需要实时一致性的场景CopyOnWriteArrayList是不能使用的。


5.2. CopyOnWriteArrayList小结

  1. CopyOnWriteArrayList适用于读多写少的场景
  2. 在并发操作容器对象时不会抛出ConcurrentModificationException,并且返回的元素与迭代器创建时的元素是一致的
  3. 容器对象的复制需要一定的开销,如果对象占用内存过大,可能造成频繁的YoungGC和Full GC
  4. CopyOnWriteArrayList不能保证数据实时一致性,只能保证最终一致性
阅读全文
1 0
原创粉丝点击