Java 并发集合:CopyOnWrite 写时复制集合介绍
在 Java 的并发编程中,传统的集合类(如 ArrayList
、HashMap
等)在多线程环境下使用时,可能会导致数据不一致和并发异常的问题。为了解决这个问题,Java 提供了一些并发集合类,其中之一就是 CopyOnWriteArrayList
。
1. 什么是 CopyOnWriteArrayList?
CopyOnWriteArrayList
是一种实现了 List
接口的线程安全集合,属于 Java 的并发集合(java.util.concurrent
包)。它的核心特点是:在对集合进行修改(比如添加、删除元素)时,会将当前集合的快照复制一份,然后在复制的快照上进行修改。因此,这种策略被称为“写时复制”。
这种设计理念有几个明显的优势:
- 读操作的要素不需要加锁,读取数据时不受写操作的影响,所以读性能非常高。
- 在写操作非常少、读操作非常多的场景下,使用 CopyOnWriteArrayList
可以有效降低锁的竞争,提高整体并发性能。
2. CopyOnWriteArrayList 的使用示例
下面是 CopyOnWriteArrayList
的一个简单示例,展示了如何在多线程环境下使用它:
import java.util.concurrent.CopyOnWriteArrayList;
public class CopyOnWriteExample {
public static void main(String[] args) {
// 创建一个 CopyOnWriteArrayList
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
// 向集合中添加元素
list.add("A");
list.add("B");
list.add("C");
// 创建多个线程,模拟并发读取
Runnable readTask = () -> {
for (String item : list) {
System.out.println(Thread.currentThread().getName() + "读取: " + item);
try {
Thread.sleep(100); // 模拟读取延迟
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
};
// 创建多个线程,模拟并发添加
Runnable writeTask = () -> {
for (int i = 0; i < 3; i++) {
String newItem = "D" + i;
list.add(newItem);
System.out.println(Thread.currentThread().getName() + "添加: " + newItem);
try {
Thread.sleep(150); // 模拟写入延迟
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
};
// 启动多个读取线程
Thread readThread1 = new Thread(readTask, "读取线程1");
Thread readThread2 = new Thread(readTask, "读取线程2");
readThread1.start();
readThread2.start();
// 启动多个写入线程
Thread writeThread1 = new Thread(writeTask, "写入线程1");
Thread writeThread2 = new Thread(writeTask, "写入线程2");
writeThread1.start();
writeThread2.start();
try {
readThread1.join();
readThread2.join();
writeThread1.join();
writeThread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3. 解释代码
在上述示例中,我们创建了一个 CopyOnWriteArrayList
并添加了初始元素 'A', 'B', 'C'。随后,我们定义了两个任务,一个是读取列表内容的线程,另一个是往列表中添加新元素的线程。
-
读取任务 (
readTask
): 这个任务循环读取集合中的元素,并打印当前线程的名称和读取的元素。由于CopyOnWriteArrayList
的特性,读取时不需要额外的同步。 -
写入任务 (
writeTask
): 这个任务循环添加元素到集合中,每次添加都会创建集合的一个新副本。
在多线程的情况下,读取线程和写入线程会同时运行,读取线程能够读取到添加之前的元素,以及在添加完成后新添加的元素。
结论
CopyOnWriteArrayList
是一个在多读少写场景下非常优雅的解决方案。虽然写操作可能会造成额外的性能开销(因为每次写入都要复制整个数组),但在读操作频繁的情况下,CopyOnWriteArrayList
提供了良好的性能表现。对于需要保持线程安全且读写操作不平衡的应用场景,选择 CopyOnWriteArrayList
在许多情况下都是一个明智的选择。