CopyOnWriteArrayList概述
CopyOnWriteArrayList是ArrayList的一个线程安全的变种。
CopyOnWriteArrayList与ArrayList不同处就在于是否会拷贝数组和加锁。
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 semantics setArray(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 GC和Full GC,也更说明了CopyOnWriteArrayList更适合于读取,而不适合与大量修改。
参考:
版权声明:本文为博主原创文章,转载请注明出处。