解释一下CopyOnWriteArrayList?它有哪些特点?
参考回答**
CopyOnWriteArrayList
是 Java 并发包(java.util.concurrent
)中的一个线程安全的集合类,基于 写时复制(Copy-On-Write)机制实现。它适用于 读多写少 的场景,能够在多线程环境下提供安全的并发访问。
特点
- 线程安全:
CopyOnWriteArrayList
是线程安全的,因为在写操作(如添加、修改、删除)时,它会创建一个新的数组副本,写操作完成后再将新数组替换为原数组。
- 读操作无锁:
- 读操作直接访问底层数组,因此不需要加锁,性能非常高。
- 写操作开销大:
- 写操作时会创建数组的副本,因此开销较大,不适合写操作频繁的场景。
- 快照迭代:
- 使用迭代器遍历时,
CopyOnWriteArrayList
迭代的是一个快照,快照内容不会受到其他线程写操作的影响,因此不会抛出ConcurrentModificationException
。
- 使用迭代器遍历时,
- 适用场景:
- 适合 读多写少 的场景,例如缓存、配置管理、观察者列表等。
详细讲解与实现机制
1. 写时复制机制
- 在执行写操作时(如 add(、set()或 remove(),CopyOnWriteArrayList会:
- 创建当前底层数组的副本。
- 在副本上执行写操作。
- 将修改后的副本替换为原数组。
- 因为写操作是基于副本完成的,读操作仍然可以访问未修改的原数组,不需要等待写操作完成,从而实现线程安全。
示例代码:
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
list.add("C");
// 写操作:创建副本并更新
list.add("D"); // 创建新数组并添加 "D"
2. 快照迭代
CopyOnWriteArrayList
的迭代器是基于 快照(snapshot)的:
- 在创建迭代器时,会复制当前数组。
- 迭代过程中,其他线程对列表的修改不会影响迭代器。
因此,CopyOnWriteArrayList
的迭代器不会抛出 ConcurrentModificationException
。
示例代码:
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
for (String s : list) {
System.out.println(s);
list.add("C"); // 修改列表,但不会影响迭代器
}
输出结果:
A
B
注意:迭代器的快照不会反映修改后的列表内容。
3. 线程安全的实现
CopyOnWriteArrayList
的线程安全是通过 写时复制 实现的,以下是常见方法的内部逻辑:
(1) add(E e) 方法
写操作会创建副本:
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(); // 释放锁
}
}
(2) get(int index) 方法
读操作直接访问底层数组,无需加锁:
public E get(int index) {
return (E) getArray()[index];
}
优缺点分析
优点:
- 线程安全:通过写时复制机制实现线程安全,适合并发环境。
- 读性能高:读操作不需要加锁,性能优于传统同步集合(如
Vector
)。 - 迭代安全:迭代器基于快照,不会抛出
ConcurrentModificationException
。
缺点:
- 写操作开销大:每次写操作都会创建数组副本,内存占用高,性能低。
- 内存敏感:如果数组较大,写操作会占用大量内存。
- 不适合写频繁的场景:频繁的写操作可能导致性能瓶颈。
适用场景
CopyOnWriteArrayList
适用于以下场景:
- 读多写少的场景:
- 例如:缓存、配置列表、观察者模式中的订阅者列表等。
- 需要线程安全且读操作较多:
- 例如:多线程访问共享数据,但写操作很少。
与其他集合类的对比
特性 | ArrayList |
Vector |
CopyOnWriteArrayList |
---|---|---|---|
线程安全性 | 非线程安全 | 线程安全(加锁) | 线程安全(写时复制) |
读操作性能 | 高 | 低(加锁) | 高(无锁) |
写操作性能 | 高(直接修改) | 低(加锁) | 低(需要复制数组) |
迭代器类型 | fail-fast | fail-fast 或 Enumeration | 快照(不抛出 ConcurrentModificationException ) |
适用场景 | 单线程环境 | 多线程写多读少 | 多线程读多写少 |
示例代码
以下代码展示了 CopyOnWriteArrayList
的基本用法:
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteArrayListExample {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
// 添加元素
list.add("A");
list.add("B");
list.add("C");
// 多线程读取
Runnable readTask = () -> {
for (String s : list) {
System.out.println(Thread.currentThread().getName() + " - " + s);
}
};
// 多线程写入
Runnable writeTask = () -> {
list.add("D");
System.out.println(Thread.currentThread().getName() + " - Added D");
};
// 创建线程
Thread t1 = new Thread(readTask, "Reader-1");
Thread t2 = new Thread(writeTask, "Writer-1");
// 启动线程
t1.start();
t2.start();
}
}
输出示例:
Reader-1 - A
Reader-1 - B
Reader-1 - C
Writer-1 - Added D
总结
CopyOnWriteArrayList
是线程安全的集合类,基于写时复制机制实现,适合 读多写少 的场景。- 它的核心特点是 写操作创建数组副本 和 快照迭代,在读操作频繁的多线程环境下性能表现优异。
- 对于写操作频繁的场景,应避免使用
CopyOnWriteArrayList
,而选择其他并发集合类(如ConcurrentHashMap
或Collections.synchronizedList()
)。