标签:java 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();
}
}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();
}
}
数据一致性问题:如果一个线程正在修改,而另一个线程此刻便读取的是旧数据,也就是说不能保证数据的一致性。
内存问题:
在添加、修改、删除的操作中,会大量的复制数组,当内存很小时,会在内存中产生许多新数组,从而提前触发一系列的young GC和Full GC,也更说明了CopyOnWriteArrayList更适合于读取,而不适合与大量修改。
版权声明:本文为博主原创文章,转载请注明出处。
深入集合框架之CopyOnWriteArrayList源码剖析
标签:java copyonwritearraylist 源码 集合
原文地址:http://blog.csdn.net/u014307117/article/details/47423373