一、基本思想
ArrayList的一个变体,通过对原来数组的拷贝,来保证不同操作情况下的线程安全。
这个容器类内部有大量的数组拷贝操作
二、源码解析
2.1 基本数据结构
/** The lock protecting all mutators */
transient final ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
private volatile transient Object[] array;
很简单,里面一个数组而已,声明成volatile保证一个线程的修改对其它线程立即可见
2.2 get操作
public E get(int index) {
return (E)(getArray()[index]); //数组的简单遍历操作,没有锁,没有拷贝
}
final Object[] getArray() {
return array;
}
public Object[] toArray()//返回的是一个数组副本
Object[] elements = getArray();
return Arrays.copyOf(elements, elements.length);
}
2.3 add操作
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;
if (numMoved == 0)
newElements = Arrays.copyOf(elements, len + 1); //不需要移动,直接拷贝一个新的数组
else {
newElements = new Object[len + 1];
System.arraycopy(elements, 0, newElements, 0, index); //拷贝待移动之前的数组
System.arraycopy(elements, index, newElements, index + 1,
numMoved);//拷贝待移动的数组
}
newElements[index] = element;//设置值
setArray(newElements);//将新的数组作为修改后的数组
} finally {
lock.unlock();//释放锁放在final中
}
}
2.4 remove 操作
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object oldValue = 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 (E)oldValue;
} finally {
lock.unlock();
}
}
很简单,和add操作基本一样
三、适用范围
官方解释,来自JDK源码中的注释。
尽管数组的大量拷贝的代价很大,但是在某些情况下可能会更加有效。比如说你需要用多个线程对数组大量的遍历,而你又不想同步你的遍历的时候。
因为你每次遍历其实是你当前数组的一个快照,所以你的程序永远不会出现"ConcurrentModifacationException"。
内存一致性问题:一个线程在处理这个数组的时候都是处理这个数组的副本,因此线程在处理过程中的这段时间,无法获得其它的线程对此数组的更改。
四、测试
//TODO :)