并发集合-CopyOnWrite系列
CopyOnWriteArrayList
CopyOnWriteArrayList 是 Java 并发包 (java.util.concurrent) 中提供的一个线程安全的 ArrayList 实现,它采用了"写时复制"(Copy-On-Write, COW)技术来实现线程安全。
核心特点
- 线程安全:无需额外同步即可在多线程环境下安全使用
- 写时复制:任何修改操作(add, set, remove等)都会创建底层数组的新副本
- 弱一致性:为了尽可能保证并发性能(读写不阻塞),不是时时刻刻都一致,但最终是一致的
- 读操作无锁:读取操作不需要加锁,性能很高
类结构
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
final transient ReentrantLock lock = new ReentrantLock();
private transient volatile Object[] array;
}
- 实现了
RandomAccess接口,说明支持随机访问,也说明数据结构是数组了(同ArrayList) - 实现了
List接口,具有add()、remove()、contains()、get()等方法(同ArrayList) - 有个
ReenTrantLock类型的lock属性,用来保证写写互斥的
工作原理
add() 添加元素
- 添加元素是写操作,所以要获取锁才能进行
- 核心就是复制一个新数组出来,然后添加元素到这个新数组,最后用这个新数组替换原来的数组
- 只要是写操作的方法都要先获取锁,比如
clear()、add(index, element)等
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();
}
}
get() 获取元素
获取元素更简单了,直接通过下标获取元素,完全不做并发安全的操作
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
弱一致性
读不加锁,写加锁,读写可以并行,尽可能提高并发性能,虽然并发是高了,但是也带来一个问题:
| 线程A | 线程B |
|---|---|
| get(0) | remove(0) |
| setArray() | |
| get(0) |
两次读不一致,数据库的读已提交隔离级别造成的不可重复读问题
虽然表现比较一致,但是要明白 CopyOnWriteArrayList 是有意为之,故意这么做的!
迭代器弱一致性(也是副本):
public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<Integer>(){{
add(1);
add(2);
add(3);
}};
// 迭代器也是副本,下面 t1 线程删除了第一个元素,但是不影响副本,所以主线程里第一个元素还是1
Iterator<Integer> iterator = list.iterator();
Thread t1 = new Thread(() -> list.remove(0)); // 删除第一个元素
t1.join(); // 让 t1 执行完再接着执行主线程
while (iterator.hasNext()){
System.out.println(iterator.next());
}
}
不要觉得弱一致性不好
- 数据库的【读已提交】这个隔离级别也不是就不好,并发和一致性天生就是矛盾的,就看如何取舍
- 相比【读已提交】这个隔离界别,用【MVCC】类比应该更形象
适用场景
- 读多写少:适合读取操作远多于修改操作的场景
- 集合较小:因为每次修改都要复制整个数组,大集合性能较差
优缺点
优点:
- 读取性能极高(无锁)
- 迭代安全,不会抛出并发修改异常
- 实现简单,易于理解
缺点:
- 写操作性能较差(需要复制整个数组)
- 内存占用较大(写操作时会有两个数组存在)
- 数据实时性不强,读取操作可能获取不到最新数据
与同步List比较
相比于使用Collections.synchronizedList()包装的ArrayList:
CopyOnWriteArrayList在读多写少场景下性能更好- 同步List在迭代时需要外部同步,否则可能抛出异常
- 同步List的读写都是同步的,而COW只有写操作需要同步
CopyOnWriteArrayList是Java并发编程中一个非常有用的工具类,特别适合读多写少的并发场景。
CopyOnWriteArraySet
内部使用 CopyOnWriteArrayList 实现的,在添加元素时先判断是否有重复元素,没有才添加
public class CopyOnWriteArraySet<E> extends AbstractSet<E>
implements java.io.Serializable {
private final CopyOnWriteArrayList<E> al;
public boolean add(E e) {
return al.addIfAbsent(e);
}
}

浙公网安备 33010602011771号