侧边栏壁纸
博主头像
拾荒的小海螺博主等级

只有想不到的,没有做不到的

  • 累计撰写 194 篇文章
  • 累计创建 19 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

JAVA:探讨 CopyOnWriteArrayList 的详细指南

拾荒的小海螺
2024-11-08 / 0 评论 / 0 点赞 / 4 阅读 / 5315 字

1、简述

在 Java 的并发编程中,CopyOnWriteArrayList 是一种特殊的线程安全的集合类。它位于 java.util.concurrent 包中,主要用于在并发读写场景下提供稳定的性能。与传统的 ArrayList 不同,CopyOnWriteArrayList 通过在每次修改时创建一个底层数组的新副本,确保了读操作的高效和线程安全性。

本文将详细探讨 CopyOnWriteArrayList 的工作原理、优缺点以及适用的场景。

image-ebvh.png

2、工作原理

每当执行写操作(如添加、删除或更新元素)时,CopyOnWriteArrayList 都会创建一个新的数组副本,更新操作在新的数组上进行,而读操作则可以继续使用旧的数组,不会受到影响。这种设计确保了读操作与写操作互不干扰,从而避免了线程同步锁的开销。

1731025962229.jpg

例如,以下代码展示了如何在 CopyOnWriteArrayList 中添加元素:

CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("Hello");
list.add("World");

在每次调用 add() 方法时,CopyOnWriteArrayList 会复制现有的数组,并在新的数组中添加元素。这使得修改操作非常安全,但代价是内存消耗会增加。

3、应用样例

CopyOnWriteArrayList 是 Java 中的线程安全集合,特别适合于读多写少的场景。它的底层实现是写时复制,即在对集合执行修改操作(如 add、remove 等)时,会创建一个集合的副本,所有修改在副本上进行,修改完成后将副本替换为主集合。这种特性保证了读操作可以不加锁,并在大部分时间内保持高效。

3.1 在迭代期间修改列表

CopyOnWriteArrayList 的一个主要优点是它允许在迭代期间进行修改(如添加或删除元素),而不会抛出 ConcurrentModificationException。

public class ModifyDuringIteration {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        list.add("A");
        list.add("B");
        list.add("C");

        // 在迭代时修改列表
        for (String item : list) {
            if ("B".equals(item)) {
                list.add("D");
            }
            System.out.println(item);
        }

        System.out.println("List after modification: " + list);
    }
}

在这里,迭代过程中添加新元素是安全的,迭代器将不感知到修改(它只读取最初的快照)。最终输出的列表将包含新添加的元素 D。

3.2 高并发下的读多写少场景

CopyOnWriteArrayList 适合多线程的读多写少场景,比如缓存配置或者白名单等不经常改变的数据列表:

import java.util.concurrent.CopyOnWriteArrayList;

public class MultiThreadAccess {
    public static void main(String[] args) {
        CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();

        // 初始化一些数据
        for (int i = 0; i < 1000; i++) {
            list.add(i);
        }

        // 多线程读写
        Runnable readTask = () -> {
            for (Integer num : list) {
                // 进行一些处理
            }
        };

        Runnable writeTask = () -> {
            list.add(1001);
            list.remove(Integer.valueOf(0));
        };

        // 启动多个线程
        for (int i = 0; i < 10; i++) {
            new Thread(readTask).start();
            new Thread(writeTask).start();
        }
    }
}

在此场景下,读线程不会被阻塞,而写线程也能在读操作中不产生锁竞争的问题,从而实现高效访问。

4、应用场景

4.1 优点

  • 线程安全:CopyOnWriteArrayList 提供了内置的线程安全机制,适用于多线程环境。在不需要显式同步的情况下,多个线程可以安全地读取数据。
  • 读取效率高:由于读操作不需要加锁或同步,它的读取性能非常高,适合频繁读取的场景。
  • 无并发修改异常:传统的 ArrayList 在多线程环境下迭代时,如果同时发生修改操作,会抛出 ConcurrentModificationException。而 CopyOnWriteArrayList 在迭代时,使用的是修改前的快照(即副本),不会抛出异常。
  • 迭代时的安全性:由于迭代时访问的是数组的快照,读操作不会受到写操作的干扰,这就避免了在遍历过程中由于写操作导致的不一致性。

4.2 缺点

  • 内存开销大:每次修改时都会创建数组的副本,因此在写操作频繁时,内存开销会变得非常大。特别是当列表较大时,频繁的写操作会占用大量内存。
  • 写操作性能较差:由于每次写操作都需要复制整个数组,因此 CopyOnWriteArrayList 的写操作效率较低,特别是在有大量修改时性能会显著下降。
  • 延迟看到修改:写操作后,其他线程在某一时刻可能还在读取旧的数组快照,而不是立刻看到最新的修改。

4.3 适用场景

  • 读多写少的场景:CopyOnWriteArrayList 的设计非常适合读操作远多于写操作的场景。如果写操作非常频繁,CopyOnWriteArrayList 可能并不合适,应该考虑其他线程安全的集合。
  • 事件监听器:在某些事件驱动的系统中,如 GUI 应用程序或服务器监听器注册表,监听器的列表经常被读取,但修改(如添加或移除监听器)的次数较少。此时,CopyOnWriteArrayList 能提供良好的性能。
  • 缓存系统:在某些缓存场景中,缓存的数据可能会频繁地被读取,但数据更新操作较少。此时,CopyOnWriteArrayList 的特性能够避免缓存更新时的锁竞争问题。
  • 迭代操作场景:如果应用程序需要频繁地迭代列表,并且不希望迭代时受到并发修改的影响,CopyOnWriteArrayList 是一个不错的选择,因为它的迭代器基于快照机制,不会抛出 ConcurrentModificationException。

5、总结

CopyOnWriteArrayList 是 Java 并发集合库中一个非常实用的工具,尤其适用于读多写少的场景。它通过牺牲写操作的性能和内存开销,换取了读操作的高效和线程安全性。这使得它在某些特定的应用场景中表现得非常优越,比如事件监听器、缓存系统和频繁迭代的环境。然而,对于写操作频繁的场景,应该谨慎使用,以避免性能瓶颈和内存问题。

选择 CopyOnWriteArrayList 时,关键在于权衡读写操作的比例。如果读操作远多于写操作,CopyOnWriteArrayList 将是一个优秀的选择。

0

评论区