java 源码中该类的注释
A thread-safe variant of ArrayList
in which all mutative operations (add
, set
, and so on) are implemented by making a fresh copy of the underlying array.
This is ordinarily too costly, but may be more efficient than alternatives when traversal operations vastly outnumber mutations, and is useful when you cannot or don't want to synchronize traversals, yet need to preclude interference among concurrent threads. The "snapshot" style iterator method uses a reference to the state of the array at the point that the iterator was created. This array never changes during the lifetime of the iterator, so interference is impossible and the iterator is guaranteed not to throw ConcurrentModificationException
. The iterator will not reflect additions, removals, or changes to the list since the iterator was created. Element-changing operations on iterators themselves (remove
, set
, and add
) are not supported. These methods throw UnsupportedOperationException
.
All elements are permitted, including null
.
Memory consistency effects: As with other concurrent collections, actions in a thread prior to placing an object into a CopyOnWriteArrayList
happen-before actions subsequent to the access or removal of that element from the CopyOnWriteArrayList
in another thread.
CopyOnWrite 写时复制,就像类注释描述的一样,这是一个线程安全的ArrayList,它在修改时会拷贝一份底层数组的数据 来保证线程安全。
复制整个底层数据很耗费时间,所以该类适用于 多读少写的场景。
源码解读
java版本 java 1.8.0_202
关键字段
/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
lock 用来保证 多个线程修改 数据的互斥,array用于存放数据。
add 方法
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// 拷贝一份数据
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
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();
}
}
可以看到添加数据是 数据长度直接扩展为原先的长度+1,这个和ArrayList扩展的长度不一样,因为CopyOnWriteArrayList每次add都需要拷贝数据,所以预留 长度没有意义。
add(int index, E element) 方法,如果插入的数据在中间,那么分两段拷贝,空出index位置。
get方法
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
final Object[] getArray() {
return array;
}
直接从array中获取对应的数据
iterator
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;
}
}
使用snapshot指向当前的array,其他线程 修改数据时 不会影响snapshot保存的数据,只是重新使用新的array。
使用案例
public static void main(String[] args) {
List<Integer> list = new CopyOnWriteArrayList<>();
list.add(1);
list.add(2);
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()){
list.add(3);
System.out.println( iterator.next());
}
}
控制台打印出 1和2,没有抛出ConcurrentModificationException,遍历的时候使用的是快照数据。
扩展
CopyOnWriteArrayList 适用于读多写少的情况,之前适用于读多写少的还有读写锁,读写锁在写时会阻塞读操作,CopyOnWriteArrayList读操作完全不会阻塞。
缺点
- CopyOnWriteList 一份数据在add方法调用过程中,遍历方法时 会复制多份数据,会导致内存占用较多。
- 写的数据不能马上读取到,因为遍历读取数据使用的是之前数据的快照,存在数据一致性问题。
网友评论