问答题788/1053CopyOnWriteArrayList是什么?

难度:
2021-11-02 创建

参考答案:

CopyOnWriteArrayList 是 Java 中的一个线程安全的 List 实现,属于 java.util.concurrent 包。它是一种 写时复制(Copy-on-Write) 的数据结构,适用于多读少写的场景。与 ArrayList 类似,CopyOnWriteArrayList 允许多个线程并发访问,但它通过在每次修改(写)操作时复制整个数组,从而保证线程安全。

1. 工作原理

CopyOnWriteArrayList 的核心思想是:每当有线程对列表进行修改(如 add()remove()set() 等)时,都会创建当前数组的副本,然后在副本上进行修改。修改完成后,副本替换原有数组。由于这种机制,所有读操作(如 get())都不需要加锁,因为它们总是读取到一个一致的、不可变的数组。

这种设计方式的优势是,读操作非常高效且线程安全,因为读操作不需要锁;但代价是,写操作的性能较差,因为每次写操作都需要复制整个数组。

2. 适用场景

CopyOnWriteArrayList 特别适用于 读多写少 的场景,比如:

  • 缓存系统:多个线程频繁读取数据,但数据更新相对较少。
  • 事件监听器:如果你需要频繁地通知多个监听器,并且很少修改监听器的列表,使用 CopyOnWriteArrayList 可以避免加锁带来的性能问题。
  • 多线程应用中的只读数据结构:当多个线程需要访问同一数据,并且不需要频繁修改数据时,CopyOnWriteArrayList 是一个理想的选择。

3. 优缺点

优点:

  • 线程安全:读操作不需要加锁,可以在多个线程中并发执行。
  • 简化开发:由于是线程安全的,使用时无需显式的同步代码块或锁机制。
  • 高效的读操作:由于没有锁,读取操作通常比锁定的列表实现(如 VectorCollections.synchronizedList())更高效。

缺点:

  • 写操作性能较差:每次修改都会创建数组的副本,造成较高的内存和性能开销。对于频繁的写操作,这种设计可能不适用。
  • 内存消耗较高:每次写操作都会复制整个数组,如果数组很大,复制的开销会很大,占用较多内存。

4. 常用方法

CopyOnWriteArrayList 的常用方法和 ArrayList 类似,它提供了 add()remove()get() 等方法。唯一的区别是,写操作会复制数组:

  • add(E e):将元素 e 添加到列表的末尾,会创建一个新副本并进行插入。
  • remove(int index):移除指定位置的元素,会创建一个新副本并进行删除。
  • get(int index):获取指定位置的元素,这是一个线程安全的读操作,不需要加锁。
  • set(int index, E element):用 element 替换指定位置的元素,会创建一个新副本并进行替换。

5. 代码示例

1import java.util.concurrent.CopyOnWriteArrayList; 2 3public class CopyOnWriteArrayListExample { 4 public static void main(String[] args) { 5 // 创建一个 CopyOnWriteArrayList 6 CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>(); 7 8 // 添加元素 9 list.add("Apple"); 10 list.add("Banana"); 11 list.add("Cherry"); 12 13 // 打印当前列表 14 System.out.println("List: " + list); 15 16 // 多线程并发访问 17 Thread readerThread = new Thread(() -> { 18 for (String item : list) { 19 System.out.println("Read item: " + item); 20 } 21 }); 22 23 Thread writerThread = new Thread(() -> { 24 list.add("Date"); 25 System.out.println("Added new item: Date"); 26 }); 27 28 // 启动线程 29 readerThread.start(); 30 writerThread.start(); 31 32 try { 33 readerThread.join(); 34 writerThread.join(); 35 } catch (InterruptedException e) { 36 e.printStackTrace(); 37 } 38 39 // 最终列表 40 System.out.println("Final List: " + list); 41 } 42}

在这个例子中,读操作不会被阻塞,即使写操作正在执行。由于 CopyOnWriteArrayList 通过复制数组来进行修改,读操作总是读取到一个一致的数组状态。

最近更新时间:2024-12-12