并发包中的并发List只有CopyOnWriteArrayList。CopyOnArrayList是一个线程安全的ArrayList,对其进行修改的操作都是在底层的一个复制的数组上进行的,也就是使用了写时复制策略。
public CopyOnWriteArrayList() {
setArray(new Object[0]);
}
无参构造函数内部创建了一个大小为0的Object数组作为array的初始值。然后看下有参构造函数:
// 如果是普通的 list ,都会重新拷贝一份,不会影响原来的 list
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
elements = c.toArray();
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
setArray(elements);
}
//创建list,其内部元素是入参toCopyIn的副本
public CopyOnWriteArrayList(E[] toCopyIn) {
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
尾部添加
public boolean add(E e) {
//(1)获取独占锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// (2)得到所有的原数组
Object[] elements = getArray();
int len = elements.length;
//(3)拷贝到新数组里面,新数组的长度是 + 1 的
Object[] newElements = Arrays.copyOf(elements, len + 1);
//在新数组中进行赋值,新元素直接放在数组的尾部
newElements[len] = e;
//(4)替换原来的数组
setArray(newElements);
return true;
} finally {
//(5)释放独占锁
lock.unlock();
}
}
从上get到:
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 {
// 如果要插入的位置在数组的中间,就需要拷贝 2 次
// 第一次从 0 拷贝到 index。
// 第二次从 index+1 拷贝到末尾。
newElements = new Object[len + 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
// index 索引位置的值是空的,直接赋值即可。
newElements[index] = element;
setArray(newElements);
} finally {
lock.unlock();
}
}
从上get到:
删除操作和add方法类似,这里看下批量删除操作:
public boolean removeAll(Collection<?> c) {
if (c == null) throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// 说明数组有值
if (len != 0) {
// newlen 表示新数组的索引位置
int newlen = 0;
Object[] temp = new Object[len];
// 循环,把不包含在 c 里面的元素,放到新数组中
for (int i = 0; i < len; ++i) {
Object element = elements[i];
// 不包含在 c 中的元素,从 0 开始放到新数组中
if (!c.contains(element))
temp[newlen++] = element;
}
// 拷贝新数组,变相的删除了不包含在 c 中的元素
if (newlen != len) {
setArray(Arrays.copyOf(temp, newlen));
return true;
}
}
return false;
} finally {
lock.unlock();
}
}
从上get到:
批量删除操作并不会直接对数组中的元素进行挨个删除,而是先将数组中的值进行循环判断,把我们不需要删除的数组放到临时数组中,最后临时数组中的数据就是我们不需要删除的数据。这样提升了性能
弱一致性是指返回迭代器后,其它线程对list的增删改对迭代器是不可见的。
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
static final class COWIterator<E> implements ListIterator<E> {
//array的快照
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;
}
@SuppressWarnings("unchecked")
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
}
如果没有其它线程对list进行增删改,那么snapshot本身就是list的array,因为他们是引用关系。但是如果在遍历期间其它线程对该list进行增删改,那么snapshot就是快照了,因为在增删改之后list里面的数组就被新数组替换了,它们操作的是两个不同的数组,这就是弱一致性。