最近正在复习Java八股,所以会将一些热门的八股问题,结合ai与自身理解写成博客便于记忆。
一、CopyOnWriteArrayList概述
CopyOnWriteArrayList是Java并发包(`java.util.concurrent`)中的一个线程安全的List实现,它采用了"写时复制"(Copy-On-Write)策略来保证线程安全。
基本特性:
- 线程安全,无需额外同步
- 适用于读多写少的场景
- 迭代器不会抛出ConcurrentModificationException
- 每次修改操作都会创建底层数组的新副本
二、核心实现原理
1. 数据结构
CopyOnWriteArrayList内部使用一个volatile数组来存储元素:
private transient volatile Object[] array;
volatile关键字保证了数组引用的可见性,任何线程都能看到最新的数组引用。
2. 读操作实现
读操作(如get、size等)直接访问当前数组,无需任何同步控制:
public E get(int index) {
return get(getArray(), index);
}
private E get(Object[] a, int index) {
return (E) a[index];
}
由于数组本身是不可变的(每次修改都会创建新数组),所以读操作是线程安全的。
3. 写操作实现
写操作(如add、set、remove等)会加锁并创建新数组:
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();
}
}
写操作的典型流程:
1. 获取独占锁
2. 复制原数组到新数组
3. 在新数组上执行修改
4. 将新数组设置为当前数组
5. 释放锁
4. 迭代器实现
CopyOnWriteArrayList的迭代器是对创建迭代器时的数组快照进行操作:
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
由于迭代器操作的是不变的数组快照,所以不会抛出ConcurrentModificationException,但也无法反映创建迭代器后的修改。
三、关键设计思想
1. 读写分离
- 读操作:无锁,直接访问当前数组
- 写操作:加锁,操作数组副本
2. 最终一致性
写操作对数组的修改对其他线程不是立即可见的,直到新数组被设置。这提供了最终一致性而非强一致性。
3. 空间换时间
通过牺牲写操作的性能(复制数组)来换取读操作的高性能。
四、适用场景
CopyOnWriteArrayList最适合以下场景:
1. 读多写少:如事件监听器列表、配置信息等
2. 集合大小通常保持较小:因为复制大数组开销大
3. 需要避免迭代时的并发修改异常
五、性能分析
优点:
- 读操作性能极高,接近普通ArrayList
- 线程安全且无需额外同步
- 迭代安全,不会抛出ConcurrentModificationException
缺点:
- 写操作性能差,每次修改都要复制整个数组
- 内存占用高,特别是集合较大时
- 数据一致性是最终一致性,不能保证强一致性