CopyOnWriteArrayList
并发包中的并发List
只有CopyOnWriteArrayList
。CopyOnWriteArrayList
是一个线程安全的ArrayList
,对其进行的修改操作都是在底层的一个复制的数组(快照)上进行的,也就是使用了写时复制策略;
写时复制策略产生的弱一致性问题
在CopyOnWriteArrayList
类图中,每个CopyOnWriteArrayList
对象里面有一个array
数组对象来存放具体元素,ReentrantLock
独占锁对象来保证同时只有一个线程对array
进行修改;
public class CopyOnWriteArrayList<E> implements List<E>{
final transient ReentrantLock lock = new ReentrantLock();
private transient volatile Object[] array;
final void setArray(Object[] a) {array = a;}
final Object[] getArray() {return array;}
public CopyOnWriteArrayList() {setArray(new Object[0]);}
}
CopyOnWriteArrayList
中用来添加元素的函数有add(E e)
、add(int index,E element)
、addIfAbsent(E e)
等,它们的原理类似,所以下面以add(E e)
举例讲解。
public boolean add(E e) {
//1. 获取独占锁(保证并发安全)
final ReentrantLock lock = this.lock;
lock.lock();
try {
//2. 获取array
Object[] elements = getArray();
int len = elements.length;
//3. 复制array到新数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
//4. 添加新元素到新数组
newElements[len] = e;
//5. 使用新数组替换添加前的数组
setArray(newElements);
return true;
} finally {
//6. 释放锁
lock.unlock();
}
}
**写时复制:**可以看到上面的案例,在向CopyOnWriteArrayList
中添加一个新元素时,CopyOnWriteArrayList
通过从原array
中复制出一个新的数组,在新复制出来的数组上进行新元素的添加;
public E get(int index) {
return get(getArray(), index);
}
//步骤A
final Object[] getArray() {
return array;
}
//步骤B
private E get(Object[] a, int index) {
return (E) a[index];
}
如上代码,可以看到。调用get(int index)
方法可以分为两个步骤,首先获取array
数组(步骤A
),然后通过下标访问指定位置的元素(步骤B
)。可以看到整个过程没有进行加锁同步,那么问题就来了。
假设,
线程X
执行完步骤A
后执行步骤B
前,另一个线程Y
进行了remove
或者add
操作,那么线程X
在执行步骤B
时的执行结果是什么样的呢?
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(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 oldValue;
} finally {
lock.unlock();
}
}
假设这个时候CopyOnWriteArrayList
中array
的内容如下所示(有1,2,3三个元素)
线程X
执行完步骤A
后执行步骤B
前,另一个线程Y
要remove
操作,删除元素1
。此时先获取独占锁,然后进行写时复制操作(如上remove代码
),在新复制的数组中删除get
需要访问的元素1
,然后让array
指向新复制的数组。这个时候线程X
开始执行步骤B
,步骤B
操作的数组是线程Y
删除之前的数组,不受影响;这其实就是写时复制策略产生的弱一致性问题;
弱一致性的迭代器
所谓的弱一致性是指返回迭代器后,其他线程对list
的增删改对迭代器不可见;
//迭代器的基本使用
public class IteratorDemo {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("test1");
list.add("test2");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
看一下CopyOnWriteArrayList
中的迭代器
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
static final class COWIterator<E> implements ListIterator<E> {
/** Snapshot of the array */
private final Object[] snapshot;
/** Index of element to be returned by subsequent call to next. */
private int cursor;
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
public boolean hasNext() {
return cursor < snapshot.length;
}
@SuppressWarnings("unchecked")
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
}
如上代码,CopyOnWriteArrayList.iterator()
获取的迭代器为COWIterator
,COWIterator
对象的snapshot
变量保存当前list
内容,cursor
是遍历list
时数据的下标;
为什么说snapshot
是list
的快照呢?明明是指针的传递引用啊,而不是副本。这个就涉及到我们之前说到的add
,remove
时的操作了。如果其他线程没有对list
进行增删改,那么snapshot
本身就是list
的array
,因为它们是引用关系。但是如果在遍历期间其他线程对list
进行了增删改,那么snapshot
就是快照了。因为增删改后list
里面的数组被新数组替换了。
list
中数组被新数组替换,老数组被snapshot
引用。获取迭代器后,由于操作的是不同数组,所以其他线程对list
进行的增删改不可见,这就是弱一致性;
public class CopyOnWriteArrayListDemo {
private static volatile CopyOnWriteArrayList copyOnWriteArrayList = new CopyOnWriteArrayList<>();
static {
copyOnWriteArrayList.add("hello");
copyOnWriteArrayList.add("hello1");
copyOnWriteArrayList.add("hello2");
copyOnWriteArrayList.add("hello3");
copyOnWriteArrayList.add("hello4");
}
public static void main(String[] args) throws InterruptedException {
Thread updateThread = new Thread(() -> {
copyOnWriteArrayList.set(0, "hello world");
copyOnWriteArrayList.remove(2);
copyOnWriteArrayList.remove(3);
}, "updateThread");
//保证在修改线程启动之前获取迭代器
Iterator iterator = copyOnWriteArrayList.iterator();
//启动线程
updateThread.start();
//main等待修改线程执行完毕
updateThread.join();
//迭代元素
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
执行结果:
hello
hello1
hello2
hello3
hello4