集合与多线程:为什么 CopyOnWriteArrayList 是多线程环境中的“秘密武器”?

举报
喵手 发表于 2025/04/29 11:51:34 2025/04/29
【摘要】 开篇语哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。  我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,...

开篇语

哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛

  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。

  我是一名后端开发爱好者,工作日常接触到最多的就是Java语言啦,所以我都尽量抽业余时间把自己所学到所会的,通过文章的形式进行输出,希望以这种方式帮助到更多的初学者或者想入门的小伙伴们,同时也能对自己的技术进行沉淀,加以复盘,查缺补漏。

小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦。三连即是对作者我写作道路上最好的鼓励与支持!

前言

嘿,亲爱的开发者们!
你是不是也有过这种困惑?在多线程环境中操作集合,常常遇到ConcurrentModificationException,就像开车打滑一样,急刹车,来不及反应,程序崩了!
但就在你快要放弃时,你发现了CopyOnWriteArrayList——它好像是一个神奇的集合,解决了多线程场景下的许多烦恼。
今天,就让我们一起探讨 CopyOnWriteArrayList,看看它如何在多线程环境下表现得如此神奇,让你轻松应对集合操作中的多线程挑战!💥


一、什么是 CopyOnWriteArrayList?

1. CopyOnWriteArrayList 的定义

CopyOnWriteArrayList 是 Java 提供的一个线程安全的 List 实现类,它属于 java.util.concurrent 包,专为高并发设计。在多线程环境下,CopyOnWriteArrayList 提供了读操作的高性能,同时通过 写时复制(copy-on-write)策略保证线程安全。

2. 工作原理:写时复制

CopyOnWriteArrayList 的核心思想是:每当你修改集合(如 add()remove()set())时,它会创建一个新副本,然后在新副本上执行操作,完成后再将引用指向这个新副本。这样,在进行写操作时,其他线程的读操作不会被影响,因为它们会继续读取原来的副本。

3. 线程安全:为什么它不需要加锁?

通过使用写时复制的策略,CopyOnWriteArrayList 实现了高效的线程安全。写操作时会复制数组,写操作和读操作互不干扰,这使得它能够在多线程环境中以较高的效率进行读操作。


二、CopyOnWriteArrayList 与其他集合的对比

1. 传统集合:ArrayListVector

  • ArrayList:不是线程安全的,多个线程同时访问和修改会引发数据不一致或 ConcurrentModificationException
  • Vector:是线程安全的,但性能相对较差,因为它通过给每个操作加锁来保证线程安全,锁的开销较大。

2. CopyOnWriteArrayList 的优势

  • 线程安全CopyOnWriteArrayList 是线程安全的,但它并不通过同步锁来实现,而是采用“写时复制”的方式,每次写操作都会复制一份新的数据,这使得它非常适合读多写少的场景。
  • 读操作高效:由于写操作不会阻塞读操作,因此在读操作占主导的情况下,性能非常高。

三、CopyOnWriteArrayList 的使用场景

1. 读多写少的场景:最适合的选择

CopyOnWriteArrayList 的最大优势在于 读操作非常高效。当你的程序中读操作远远多于写操作时,CopyOnWriteArrayList 将是一个非常好的选择。它能够避免传统锁机制(如 synchronized)的性能瓶颈,同时保证线程安全。

典型场景

  • 事件监听器:多个线程同时监听事件的情况下,CopyOnWriteArrayList 可以保证对事件列表的高效读取,而不会阻塞。
  • 缓存更新:当缓存中元素很少修改,但读取频繁时,可以使用 CopyOnWriteArrayList,避免每次读取时都加锁。

示例代码:

假设你有一个事件监听系统,需要频繁读取事件列表,但事件本身更新不频繁:

import java.util.concurrent.CopyOnWriteArrayList;

public class EventListenerExample {
    private final CopyOnWriteArrayList<String> eventListeners = new CopyOnWriteArrayList<>();

    // 添加监听器
    public void addListener(String listener) {
        eventListeners.add(listener);
    }

    // 通知所有监听器
    public void notifyListeners(String event) {
        for (String listener : eventListeners) {
            System.out.println("Notifying listener: " + listener + " about event: " + event);
        }
    }

    public static void main(String[] args) {
        EventListenerExample eventSystem = new EventListenerExample();
        
        // 添加监听器
        eventSystem.addListener("Listener 1");
        eventSystem.addListener("Listener 2");

        // 通知监听器
        eventSystem.notifyListeners("New Event");
    }
}

在这个例子中,CopyOnWriteArrayList 让我们可以非常高效地读取事件监听器,即使多个线程同时在进行读取操作。

2. 避免ConcurrentModificationException

当使用传统的 ArrayList 进行多线程遍历时,如果在遍历过程中另一个线程对 ArrayList 进行了修改,就会抛出 ConcurrentModificationException

CopyOnWriteArrayList 通过“写时复制”机制,避免了这种问题,即使在遍历过程中有线程修改集合,CopyOnWriteArrayList 也不会抛出异常,因为它实际上是在修改副本,而不是直接修改原集合。

示例代码:

import java.util.concurrent.CopyOnWriteArrayList;

public class SafeModificationExample {
    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        list.add("Item 1");
        list.add("Item 2");

        // 创建一个线程去遍历
        Thread reader = new Thread(() -> {
            for (String item : list) {
                System.out.println("Reading: " + item);
            }
        });

        // 创建另一个线程去修改
        Thread writer = new Thread(() -> {
            try {
                Thread.sleep(100);
                list.add("Item 3");
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });

        reader.start();
        writer.start();
    }
}

在这段代码中,CopyOnWriteArrayList 能够确保 reader 线程在遍历时不会因 writer 线程的修改而抛出异常。

3. 适用于频繁读取但偶尔修改的场景

如果你在某个多线程应用程序中,读取集合的操作比修改集合的操作更频繁,且修改操作相对较少,那么 CopyOnWriteArrayList 会是一个非常好的选择。它的设计使得在这种场景下的性能非常优越。

典型场景:

  • 监听器管理:如图形用户界面(GUI)中,需要通知多个监听器进行更新。
  • 线程池任务管理:任务执行队列频繁读取任务,但任务添加和删除较少。

四、CopyOnWriteArrayList 的限制与注意事项

虽然 CopyOnWriteArrayList 在多线程环境中提供了很高的性能和线程安全,但它也有一些需要注意的限制:

  1. 写操作的成本较高

    • 每次修改集合时,CopyOnWriteArrayList 都会创建集合的副本,这意味着写操作的开销比较大。如果修改操作非常频繁,可能导致性能瓶颈。
  2. 内存占用较高

    • 由于每次修改都会复制一份新的数据,所以在大量写操作时,内存的开销会增加。
  3. 不适合写多读少的场景

    • 如果你的程序中读写操作的比例大致相当,或者写操作占主导,CopyOnWriteArrayList 可能不是最佳选择。在这种情况下,传统的 ArrayListVector 更适合。

五、总结:CopyOnWriteArrayList 的应用场景和适用性

适用场景

  • 读操作远远多于写操作的场景。
  • 需要避免 ConcurrentModificationException 的场景。
  • 多线程环境下,频繁读取、少量修改的集合管理。

不适用场景

  • 写操作频繁的场景,因为每次修改都需要创建新的副本。
  • 对内存开销敏感的场景,因为副本的创建会占用额外内存。

总结
CopyOnWriteArrayList读多写少的场景下非常高效,且能避免并发修改异常。然而,如果你的程序有大量写操作,可能需要考虑其他的线程安全集合类,像 ConcurrentHashMapCollections.synchronizedList()

… …

文末

好啦,以上就是我这期的全部内容,如果有任何疑问,欢迎下方留言哦,咱们下期见。

… …

学习不分先后,知识不分多少;事无巨细,当以虚心求教;三人行,必有我师焉!!!

wished for you successed !!!


⭐️若喜欢我,就请关注我叭。

⭐️若对您有用,就请点赞叭。
⭐️若有疑问,就请评论留言告诉我叭。


版权声明:本文由作者原创,转载请注明出处,谢谢支持!

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。