CopyOnWrite

CopyOnWrite容器简介

可以理解为写时复制的容器,当向容器中添加元素时,不是直接往容器中添加,而是将当前容器进行copy,复制出一个新的容器,然后向新容器中添加元素,最后将原容器的引用指向新容器。

好处:在并发的场景下对容器进行”读操作”而不需要”加锁”,从而达到读写分离的目的。

从JDK 1.5 开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,分别是CopyOnWriteArrayList和CopyOnWriteArraySet。

CopyOnWriteArrayList

优缺点分析

优点:适用于读多写少的场景。在Java中遍历线程非安全的List时(如:ArrayList和 LinkedList),若中途有其他线程对List容器进行修改,那么会抛出ConcurrentModificationException异常。CopyOnWriteArrayList由于其“读写分离”,遍历和修改操作分别作用在不同的List容器,所以在使用迭代器遍历的时候,则不会抛出异常。

缺点:1)每次执行写操作都会将原容器拷贝一份,数据量大的时候,内存会存在较大的压力,可能会引起频繁Young GC和Full GC;2)只能保证最终数据一致性,不能保证实时一致性。

源码分析

/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#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();
}
}

/**
* Removes the element at the specified position in this list.
* Shifts any subsequent elements to the left (subtracts one from their
* indices). Returns the element that was removed from the list.
*
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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;
// 如果要删除的是列表末端数据,拷贝前len-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();
}
}

public E get(int index) {
return get(getArray(), index);
}
Author: Jiayi Yang
Link: https://jiayiy.github.io/2020/07/17/CopyOnWrite/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.