深入集合框架之CopyOnWriteArrayList源码剖析

来源:互联网 发布:mac重命名文件夹命令 编辑:程序博客网 时间:2024/05/16 15:04

CopyOnWriteArrayList概述

CopyOnWriteArrayListArrayList的一个线程安全的变种。

CopyOnWriteArrayListArrayList不同处就在于是否会拷贝数组和加锁。

CopyOnWriteArrayList顾名思义就是写时复制的ArrayList,其意思就是在修改容器的元素时,并不是直接在原数组上修改,而是先拷贝了一份数组,然后在拷贝的数组上进行修改,修改完后将其引用赋值给原数组的引用。这样体现了读写分离,这样无论在任何时候我们都可以对容器进行读取。


成员变量

这里就几个常见特性解析一下:

 transient final ReentrantLock lock = new ReentrantLock();//这便是CopyOnWriteArrayList中的锁属性,确保了在更新原数组时只有一个线程可以进来。

private volatile transient Object[] array;

这个便是原数组,我们发现与ArrayList不同的地方在于,增加了volatile属性。

这有什么用处呢?

Volaile相当于弱一级的synchronized,提供了可见性,在多线程执行环境中对每个线程都是可见的,这也就保证了所有线程拿到的数据都是一致的。

锁提供了两种级别的机制:互斥与可见

互斥是说:一个线程在修改的一个数据的时候,另一个线程不能访问这个数据。

而可见是说:一个线程在修改一个数据的时候,另一个线程拿到的值都是一致的。也就是说所有的线程都可以自动获取到volatile的最新值。

所以我们可以说synchronized在保证array写的时候的原子性,而volatile保证读array的可见性。

----------------------------------------------------------------------------------------------------------------------------

     //返回数组    final Object[] getArray() {        return array;   }           //数组复制    final void setArray(Object[] a) {        array = a;    }


读取: 

 /**     * {@inheritDoc}     *     * @throws IndexOutOfBoundsException {@inheritDoc}     */    public E get(int index) {        return (E)(getArray()[index]);    }读取的时候不会加锁,也不会拷贝数组。


写入:

    //修改数组上的某个下标的值为指定元素    public E set(int index, E element) {final ReentrantLock lock = this.lock;lock.lock();try {    Object[] elements = getArray();         //保存旧值    Object oldValue = elements[index];    if (oldValue != element) {int len = elements.length;          //拷贝一个和原数组相等长度的数组Object[] newElements = Arrays.copyOf(elements, len);           //更改拷贝数组上指定下标的值newElements[index] = element;          //让成员变量中的引用指向拷贝数组setArray(newElements);    } else {// Not quite a no-op; ensures volatile write semanticssetArray(elements);    }    return (E)oldValue;} finally {    lock.unlock();}   }              //添加元素    public boolean add(E e) {final ReentrantLock lock = this.lock;lock.lock();try {    Object[] elements = getArray();    int len = elements.length;  <span style="white-space:pre"></span>//拷贝一个比原数组长度大一个单元的数组    Object[] newElements = Arrays.copyOf(elements, len + 1);  <span style="white-space:pre"></span>//在末尾添加指定的元素    newElements[len] = e;  <span style="white-space:pre"></span>//设置引用    setArray(newElements);    return true;} finally {    lock.unlock();}    }           //移除指定下标的元素    public E remove(int index) {final ReentrantLock lock = this.lock;lock.lock();try {    Object[] elements = getArray();    int len = elements.length;  <span style="white-space:pre"></span>//保存旧值    Object oldValue = elements[index];    int numMoved = len - index - 1;    if (numMoved == 0)  <span style="white-space:pre"></span>//移除末尾的值setArray(Arrays.copyOf(elements, len - 1));    else { <span style="white-space:pre"></span> //创建新数组Object[] newElements = new Object[len - 1];  <span style="white-space:pre"></span>//从0开始拷贝index个值System.arraycopy(elements, 0, newElements, 0, index); <span style="white-space:pre"></span> //前移System.arraycopy(elements, index + 1, newElements, index, numMoved); <span style="white-space:pre"></span> //设置引用setArray(newElements);    }    return (E)oldValue;} finally {    lock.unlock();}       }

从上面的三个更新、添加、删除的方法来看,我们发现CopyOnWriteArrayList在修改原数组的过程中比ArrayList多做了2件事:

1、加锁:保证我在修改数组的时候,其他人不能修改。

2、拷贝数组:无论是哪个方法,发现都需要拷贝数组。

上面的两件事就确保了CopyOnWriteArrayList在多线程的环境下可以应对自如。

----------------------------------------------------------------------------------------------------------------------------------------------------

迭代器:


CopyOnWriteArrayList的迭代器并不是快速失败的,也就是说并不会抛出ConcurrentModificationException异常。这是因为他在修改的时候,是针对与拷贝数组而言的,对于原数组没有任何影响。我们可以看出迭代器里面没有锁机制,所以只提供读取,而不支持添加修改和删除(抛出UnsupportedOperationExcetion)。

public ListIterator<E> listIterator() {        return new COWIterator<E>(getArray(), 0);   }private static class COWIterator<E> implements ListIterator<E> {        //原数组的快照        private final Object[] snapshot;        //下标        private int cursor;        private COWIterator(Object[] elements, int initialCursor) {            cursor = initialCursor;            snapshot = elements;        }        public boolean hasNext() {            return cursor < snapshot.length;        }        public boolean hasPrevious() {            return cursor > 0;        }        public E next() {    if (! hasNext())                throw new NoSuchElementException();    return (E) snapshot[cursor++];        }        public E previous() {    if (! hasPrevious())                throw new NoSuchElementException();    return (E) snapshot[--cursor];        }        public int nextIndex() {            return cursor;        }        public int previousIndex() {            return cursor-1;        }        //不支持删除        public void remove() {            throw new UnsupportedOperationException();        }  //不支持修改        public void set(E e) {            throw new UnsupportedOperationException();        }        //不支持添加        public void add(E e) {            throw new UnsupportedOperationException();        }       }

CopyOnWriteArrayList的不足:

数据一致性问题:如果一个线程正在修改,而另一个线程此刻便读取的是旧数据,也就是说不能保证数据的一致性。

内存问题:

在添加、修改、删除的操作中,会大量的复制数组,当内存很小时,会在内存中产生许多新数组,从而提前触发一系列的young GCFull GC,也更说明了CopyOnWriteArrayList更适合于读取,而不适合与大量修改。



参考:

聊聊并发-Java中的Copy-On-Write容器

CopyOnWriteArrayList源代码之后



0 0
原创粉丝点击