• 深入理解Java中的线程安全List:CopyOnWriteArrayList原理和应用


    在这里插入图片描述

    码到三十五 : 个人主页

    心中有诗画,指尖舞代码,目光览世界,步履越千山,人间尽值得 !


    Java并发编程中,线程安全的数据结构是至关重要的。其中,CopyOnWriteArrayList是一个线程安全的ArrayList实现,它提供了在并发环境下对列表进行读写的功能。本文将深入探讨CopyOnWriteArrayList的工作原理、使用场景以及潜在的性能问题。

    目录

      • 1️⃣ 什么是CopyOnWrite(写时复制)
      • 2️⃣什么是CopyOnWriteArrayList
      • 3️⃣CopyOnWriteArrayList的工作原理
        • 3.1 读写分离的设计模式的几个优点
        • 3.2 存在的性能问题
      • 3️⃣CopyOnWriteArrayList使用场景
      • 4️⃣CopyOnWriteArrayList的应用
      • 5️⃣总结

    1️⃣ 什么是CopyOnWrite(写时复制)

    CopyOnWrite,也被称为写时复制(Copy-On-Write,简称COW),是程序设计领域中的一种优化策略。这种策略的核心思想是,当多个调用者(或线程)同时访问同一份资源时,他们会共同获取一个指向该资源的指针。只要没有调用者尝试修改这份资源,所有的调用者都可以继续访问同一个资源。但是,一旦有调用者尝试修改资源,系统就会复制一份该资源的副本给这个调用者,而其他调用者所见到的仍然是原来的资源。这个过程对其他的调用者都是透明的,他们并不知道资源已经被复制。

    在Java中,CopyOnWriteArrayList和CopyOnWriteArraySet就是使用了这种策略的两个类。这两个类都位于java.util.concurrent包下,是线程安全的集合类。当需要修改集合中的元素时,它们不会直接在原集合上进行修改,而是复制一份新的集合,然后在新的集合上进行修改。修改完成后,再将指向原集合的引用指向新的集合。这种设计使得读操作可以在不加锁的情况下进行,从而提高了并发性能。

    总的来说,CopyOnWrite是一种适用于读多写少场景的优化策略,它通过复制数据的方式实现了读写分离,提高了并发性能。但是,它也存在一些潜在的性能问题,如内存占用增加、写操作性能下降以及频繁的垃圾回收。因此,在使用时需要根据具体场景进行权衡和选择。

    2️⃣什么是CopyOnWriteArrayList

    CopyOnWriteArrayList是Java并发包java.util.concurrent中的一个类,它实现了List接口。如其名所示,

    CopyOnWriteArrayList是Java中的一个类,位于java.util.concurrent包下。它是ArrayList的一个线程安全的变体,其中所有可变操作(如add和set等)都是通过创建底层数组的新副本来实现的,因此被称为“写时复制”的列表。

    由于CopyOnWriteArrayList在遍历时不会对列表进行任何修改,因此它绝对不会抛出ConcurrentModificationException的异常。它在修改操作(如addset等)时,会复制一份底层数组,然后在新的数组上进行修改,修改完成后再将指向底层数组的引用切换到新的数组。这种设计使得读操作可以在不加锁的情况下进行,从而提高了并发性能,这个特性使得它在多线程环境下进行遍历操作时更为安全。

    然而,CopyOnWriteArrayList并没有“扩容”的概念。每次写操作(如add或remove)都需要复制一个全新的数组,这在写操作较为频繁时可能会导致性能问题,因为复制整个数组的操作是相当耗时的。因此,在使用CopyOnWriteArrayList时,需要特别注意其适用场景,一般来说,它更适合于读多写少的场景。

    3️⃣CopyOnWriteArrayList的工作原理

    CopyOnWriteArrayList是ArrayList的一个线程安全的变体。读操作可以在不加锁的情况下进行,从而提高了并发性能。

    具体来说,CopyOnWriteArrayList内部有一个可重入锁(ReentrantLock)来保证线程安全,但这个锁只在写操作时才会被使用。当进行修改操作时,线程会先获取锁,然后复制底层数组,并在新数组上执行修改。修改完成后,通过volatile关键字修饰的引用来确保新的数组对所有线程可见。由于读操作不需要获取锁,因此多个线程可以同时进行读操作,而不会相互干扰。
    在这里插入图片描述

    3.1 读写分离的设计模式的几个优点

    • 读操作性能很高

    由于读操作不需要获取锁,因此多个线程可以同时进行读操作,而不会相互干扰。这使得在高并发场景下,CopyOnWriteArrayList的读操作性能非常出色。

    • 数据一致性

    由于写操作是通过复制底层数组并在新数组上执行修改来实现的,因此不会出现多个线程同时修改同一个元素的情况。这保证了数据的一致性。

    • 适用于读多写少的场景

    由于写操作需要复制整个底层数组,因此在写操作较为频繁的场景下,CopyOnWriteArrayList的性能可能会受到较大影响。但在读多写少的场景下,它可以充分发挥其优势。

    3.2 存在的性能问题

    • 内存占用

    每次写操作都需要复制整个底层数组,这会导致内存占用增加。特别是在列表较大时,这种内存开销可能会变得非常显著。

    • 写操作性能下降

    由于每次写操作都需要复制整个数组,并在新数组上执行修改,因此写操作的性能可能会受到较大影响。特别是在高并发场景下,这种性能下降可能会更加明显。

    • 频繁的垃圾回收

    由于写操作会创建新的数组,因此可能导致频繁的垃圾回收。这可能会对系统的整体性能产生影响。

    总的来说,CopyOnWriteArrayList是一种适用于读多写少场景的线程安全列表实现。它通过复制底层数组的方式实现了读写分离,提高了读操作的并发性能。但在使用时需要根据具体场景进行权衡和选择,以避免潜在的性能问题。

    3️⃣CopyOnWriteArrayList使用场景

    CopyOnWriteArrayList适用于读多写少的场景。在这种场景下,由于读操作不需要获取锁,因此可以充分发挥多核CPU的并行计算能力,提高系统的吞吐量。然而,在写操作较为频繁的场景下,CopyOnWriteArrayList的性能可能会受到较大影响。

    4️⃣CopyOnWriteArrayList的应用

    下面是一个使用CopyOnWriteArrayList的代码,它模拟了一个简单的新闻发布系统。在这个系统中,多个线程可以并发地添加新闻和读取新闻列表。由于读操作远多于写操作,因此使用CopyOnWriteArrayList是合适的。

    import java.util.List;
    import java.util.concurrent.CopyOnWriteArrayList;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.TimeUnit;
    
    // 新闻类
    class News {
        private String title;
        private String content;
    
        public News(String title, String content) {
            this.title = title;
            this.content = content;
        }
    
        public String getTitle() {
            return title;
        }
    
        public String getContent() {
            return content;
        }
    
        @Override
        public String toString() {
            return "News{" +
                    "title='" + title + '\'' +
                    ", content='" + content + '\'' +
                    '}';
        }
    }
    
    // 新闻发布系统类
    public class NewsPublisherSystem {
        // 使用CopyOnWriteArrayList存储新闻列表
        private final List<News> newsList = new CopyOnWriteArrayList<>();
    
        // 添加新闻
        public void addNews(News news) {
            newsList.add(news);
            System.out.println("新闻已添加: " + news);
        }
    
        // 获取新闻列表
        public List<News> getNewsList() {
            return newsList;
        }
    
        // 模拟多线程添加和读取新闻
        public void simulate() {
            ExecutorService executor = Executors.newFixedThreadPool(10);
    
            // 提交5个添加新闻的任务
            for (int i = 0; i < 5; i++) {
                final int index = i;
                executor.submit(() -> {
                    for (int j = 0; j < 10; j++) {
                        News news = new News("新闻标题" + index + "-" + j, "新闻内容" + index + "-" + j);
                        addNews(news);
                        try {
                            // 模拟新闻发布的延迟
                            TimeUnit.MILLISECONDS.sleep(50);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
    
            // 提交5个读取新闻列表的任务
            for (int i = 0; i < 5; i++) {
                executor.submit(() -> {
                    for (int j = 0; j < 20; j++) {
                        System.out.println("当前新闻列表: " + getNewsList());
                        try {
                            // 模拟读取新闻列表的延迟
                            TimeUnit.MILLISECONDS.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                });
            }
    
            // 关闭执行器服务
            executor.shutdown();
            try {
                if (!executor.awaitTermination(1, TimeUnit.MINUTES)) {
                    executor.shutdownNow();
                }
            } catch (InterruptedException e) {
                executor.shutdownNow();
                Thread.currentThread().interrupt();
            }
        }
    
        public static void main(String[] args) {
            NewsPublisherSystem system = new NewsPublisherSystem();
            system.simulate();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102

    NewsPublisherSystem类维护了一个CopyOnWriteArrayList来存储新闻对象。addNews方法用于添加新闻到列表中,而getNewsList方法用于获取当前的新闻列表。

    simulate方法中,我们创建了一个固定大小的线程池,并提交了10个任务:其中5个任务用于添加新闻,另外5个任务用于读取新闻列表。每个添加新闻的任务会创建并添加10条新闻,而每个读取新闻列表的任务会读取新闻列表20次。

    由于使用了CopyOnWriteArrayList,多个线程可以同时读取新闻列表,而不会有线程安全问题。当添加新闻时,CopyOnWriteArrayList会复制底层数组,从而保证读取操作不会受到写操作的影响。

    请注意,由于CopyOnWriteArrayList在写操作时会复制整个底层数组,因此在新闻列表非常大且写操作频繁的情况下,性能可能会受到影响。在这种情况下,可能需要考虑其他并发数据结构或同步策略。然而,在本案例中,由于读操作远多于写操作,使用CopyOnWriteArrayList是合适的。

    5️⃣总结

    CopyOnWriteArrayList是Java并发编程中一个重要的线程安全列表实现。它通过复制底层数组的方式实现了读写分离,提高了读操作的并发性能。然而,它也存在一些潜在的性能问题,如内存占用增加、写操作性能下降以及频繁的垃圾回收。因此,在使用CopyOnWriteArrayList时,需要根据具体的使用场景进行权衡和选择。在读多写少的场景下,CopyOnWriteArrayList可以发挥出色的性能;而在写操作较为频繁的场景下,可能需要考虑其他线程安全的列表实现。

  • 相关阅读:
    Android 10 中的隐私权变更
    Vue 源码解读(6)—— 实例方法
    QML(25)——文本输入框组件的区别(TextField TextInput TextArea TextEdit)
    操作系统之文件存储空间管理
    基于粒子群优化算法的BP神经网络预测模型(Matlab代码实现)
    ETH网络中的区块链
    vue重修之路由【下】
    深度解读RAGFlow的深度文档理解DeepDoc
    python异常-try、except、finally、else、raise、异常的传递、自定义异常
    C 嵌入式系统设计模式 08:硬件代理模式
  • 原文地址:https://blog.csdn.net/qq_26664043/article/details/136611801