CopyOnWriteArrayList是java.util.concurrent包下的一个线程安全列表实现,它的核心设计思路是写时复制,也就是当对列表进行修改操作时,会先复制一份底层数组,在副本上完成修改后再将底层数组引用指向新的数组,从而保证读操作不需要加锁,读性能较高。

CopyOnWriteArrayList的核心原理
CopyOnWriteArrayList的底层维护了一个volatile修饰的数组,所有读操作都是直接访问这个数组,不需要任何同步措施。写操作则需要通过内部的重入锁保证同一时间只有一个线程可以执行修改逻辑,修改时会创建新的数组,将原数组内容复制过去,再添加或修改元素,最后替换原数组引用。
这种设计的好处是读操作完全无锁,性能很高,但写操作因为需要复制数组,开销相对较大,所以更适合读多写少的并发场景。
常用操作方法
创建实例
可以通过以下方式创建CopyOnWriteArrayList实例:
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.Arrays;
public class COWListDemo {
public static void main(String[] args) {
// 创建空列表
CopyOnWriteArrayList<String> list1 = new CopyOnWriteArrayList<>();
// 基于已有集合创建列表
CopyOnWriteArrayList<String> list2 = new CopyOnWriteArrayList<>(Arrays.asList("a", "b", "c"));
}
}
添加元素
添加元素的方法和普通List类似,支持add、addAll等操作:
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>(); // 添加单个元素 list.add(1); list.add(2); // 在指定位置插入元素 list.add(1, 10); // 批量添加元素 list.addAll(Arrays.asList(3, 4, 5));
读取与修改元素
读取元素可以直接通过get方法,修改元素可以使用set方法:
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(Arrays.asList("a", "b", "c"));
// 读取元素
String first = list.get(0);
// 修改指定位置元素
list.set(1, "bb");
删除元素
删除操作支持按索引删除、按元素删除等方式:
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(Arrays.asList("a", "b", "c", "b"));
// 按索引删除
list.remove(0);
// 按元素删除第一个匹配项
list.remove("b");
// 批量删除
list.removeAll(Arrays.asList("c"));
遍历操作
CopyOnWriteArrayList的迭代器是弱一致性的,迭代器创建时会基于当时的数组快照进行遍历,后续对列表的修改不会影响已经创建的迭代器:
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(Arrays.asList("a", "b", "c"));
// 使用迭代器遍历
for (String item : list) {
System.out.println(item);
}
// 使用forEach遍历
list.forEach(System.out::println);
适用场景与注意事项
适用场景
- 读操作频率远高于写操作的并发场景,比如缓存列表、配置项列表等。
- 需要遍历操作的同时允许其他线程修改列表的场景,因为迭代器基于快照,不会出现ConcurrentModificationException。
注意事项
- 写操作开销大,因为每次修改都要复制整个数组,如果列表元素数量很多,写操作的性能会很差,不适合写频繁的场景。
- 内存占用问题,写操作时会有新旧两个数组同时存在,可能导致内存占用升高,需要评估内存情况。
- 数据一致性是最终一致性,读操作可能读到旧的数据,因为写操作完成后新数组才会被其他线程可见,不适合对数据实时性要求极高的场景。
- 不要在遍历过程中对CopyOnWriteArrayList做修改操作,虽然不会抛异常,但修改不会反映到当前迭代器中,容易造成逻辑误解。
与普通ArrayList的对比
可以通过以下表格直观对比两者的差异:
| 对比项 | ArrayList | CopyOnWriteArrayList |
|---|---|---|
| 线程安全 | 非线程安全 | 线程安全 |
| 读操作性能 | 无锁,性能好 | 无锁,性能好 |
| 写操作性能 | 无额外开销,性能好 | 需要复制数组,开销大 |
| 遍历时修改 | 会抛ConcurrentModificationException | 不会抛异常,基于快照遍历 |
| 适用场景 | 单线程或外部加锁的场景 | 读多写少的并发场景 |
实践示例:多线程场景下的安全列表操作
下面是一个简单的多线程读写示例,展示CopyOnWriteArrayList在并发场景下的表现:
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class COWListConcurrentDemo {
public static void main(String[] args) throws InterruptedException {
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
ExecutorService pool = Executors.newFixedThreadPool(5);
// 启动3个写线程
for (int i = 0; i < 3; i++) {
int threadId = i;
pool.submit(() -> {
for (int j = 0; j < 5; j++) {
list.add(threadId * 10 + j);
System.out.println("线程" + threadId + "添加元素:" + (threadId * 10 + j));
}
});
}
// 启动2个读线程
for (int i = 0; i < 2; i++) {
int threadId = i;
pool.submit(() -> {
for (int j = 0; j < 10; j++) {
System.out.println("线程" + threadId + "读取列表大小:" + list.size());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
}
pool.shutdown();
}
}
运行上述代码可以看到,多个线程同时读写CopyOnWriteArrayList时不会出现数据错乱或者异常,读线程可以正常获取到列表的最新大小,写线程的添加操作也都生效。
CopyOnWriteArrayListJava线程安全并发编程列表操作修改时间:2026-07-22 17:06:31