CopyOnWriteArrayList
是 Java 并发包 (java.util.concurrent
) 中的一个类,它实现了 List
接口,并提供了线程安全的并发访问。这个类的名称“CopyOnWrite”描述了它的核心工作原理:在进行写操作(如添加、删除元素)时,它会复制底层数组,而不是在原有数组上进行修改。这种策略使得读取操作可以无锁地进行,从而提高了并发性能。
使用示例
使用 CopyOnWriteArrayList
非常简单,就像使用普通的 ArrayList
一样。以下是一个简单的示例:
import java.util.concurrent.CopyOnWriteArrayList;
public class Example {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
// 添加元素
list.add("Element 1");
list.add("Element 2");
// 读取元素
String firstElement = list.get(0);
System.out.println(firstElement); // 输出: Element 1
// 遍历元素
for (String element : list) {
System.out.println(element);
}
// 在多线程环境中使用
new Thread(() -> {
list.add("Element 3 from thread");
}).start();
// 主线程可以继续读取,不会受到写操作的影响
System.out.println(list.get(1)); // 输出可能是: Element 2 或 Element 3 from thread(取决于线程的执行顺序)
}
}
需要注意的是,由于 CopyOnWriteArrayList
的弱一致性模型,读取操作可能看不到其他线程最近进行的写入操作。
原理介绍
CopyOnWriteArrayList
的核心工作原理基于以下几点:
- 读写分离:读取操作不需要获取锁,因为它们工作在数组的一个不变的快照上。写操作则需要获取锁以执行数组复制和修改。
- 写时复制:当执行写操作(如
add
或set
)时,CopyOnWriteArrayList
会创建一个新的数组,将旧数组中的元素复制到新数组中,并应用修改。然后,它会在一个原子操作中将内部引用从旧数组切换到新数组。这样,读取操作可以继续使用旧数组,而不会受到写操作的影响。
注意事项
- 迭代器安全性:由于迭代器在创建时捕获了数组的一个快照,因此它们不会抛出
ConcurrentModificationException
,即使在迭代过程中列表被其他线程修改。 - 适用场景:
CopyOnWriteArrayList
最适合读操作远多于写操作,并且数据量不是特别大的场景。因为写操作需要复制整个数组,如果数组很大或者写操作非常频繁,那么这个类可能不是一个高效的选择。 - 内存开销:由于每次写操作都会复制底层数组,这可能会导致较大的内存开销,特别是当数组很大时。此外,频繁的写操作会导致大量的垃圾收集活动,从而影响性能。
- 弱一致性:
CopyOnWriteArrayList
提供了一种弱一致性模型,这意味着读取操作可能看不到最新的写入操作。这种一致性模型在某些场景下是可以接受的,但在需要强一致性的场景下则不适用。
总结
总之,CopyOnWriteArrayList
是一个适用于读多写少场景的线程安全列表。它的工作原理基于读写分离和写时复制策略,以提供高效的并发读取性能。然而,在设计并发系统时,需要仔细考虑其内存开销和一致性模型是否满足应用需求。