CopyOnWriteArrayList怎么用

    • 什么是CopyOnWriteArrayList
    • CopyOnWriteArrayList常用方法
    • CopyOnWriteArrayList源码详解
    • CopyOnWriteArrayList使用注意点
    • CopyOnWriteArrayList存在的性能问题
    • CopyOnWriteArrayList 使用实例
      • 基本应用实例
      • 并发应用实例
    • 拓展
      • 写时复制

在这里插入图片描述

什么是CopyOnWriteArrayList

CopyOnWriteArrayList 是一个线程安全的ArrayList,它使用了一种称为“写时复制”(Copy-on-Write)的策略来保证线程安全。

在CopyOnWriteArrayList中,每个元素都存储在一个数组中。当一个线程要对数组进行修改(例如添加、删除元素)时,它会首先复制一份当前数组的副本,对副本进行修改,然后将新的数组替换掉旧的数组。这样做的好处是,其他线程在读取数组时始终会看到一个一致的、不会改变的数组,从而避免了线程间的竞争条件。

由于CopyOnWriteArrayList采用写时复制的策略,因此在高并发的情况下可能会导致频繁的复制操作,这会消耗一定的系统资源。但是,如果读操作的频率远远高于写操作的频率,那么CopyOnWriteArrayList可以提供较好的并发性能和较高的读操作吞吐量。

总的来说,CopyOnWriteArrayList适用于读操作远多于写操作的场景,它提供了一种线程安全的解决方案,使得在并发环境下也能够保证数据的一致性和可靠性。

在这里插入图片描述

CopyOnWriteArrayList常用方法

CopyOnWriteArrayList常用的方法有:

    1. get(int index):获取指定索引位置的元素。
    1. set(int index, E element):将指定索引位置的元素替换为新元素。
    1. add(E element):在集合的末尾添加新元素。
    1. remove(Object o):从集合中移除指定的元素。
    1. size():返回集合的大小。
    1. contains(Object o):检查集合中是否包含指定的元素。
    1. iterator():返回一个迭代器,用于遍历集合中的元素。
    1. toArray():将集合转换为数组。
    1. addAll(Collection c):将指定集合中的所有元素添加到CopyOnWriteArrayList中。
    1. removeAll(Collection c):从CopyOnWriteArrayList中移除指定集合中的所有元素。
    1. retainAll(Collection c):仅保留CopyOnWriteArrayList中包含在指定集合中的元素。

这些方法可以帮助你在使用CopyOnWriteArrayList时完成更复杂的操作。需要注意的是,由于CopyOnWriteArrayList是线程安全的,因此在多线程环境下使用时需要注意并发问题。

CopyOnWriteArrayList源码详解

以下是CopyOnWriteArrayList的源码详解,让我们一起来看一下每一个步骤做的一些事情:

  1. 创建数组

在CopyOnWriteArrayList中,每个元素都存储在一个数组中。在创建CopyOnWriteArrayList时,需要传入一个初始大小。这个初始大小决定了初始数组的大小。例如,创建一个大小为10的CopyOnWriteArrayList时,会创建一个长度为10的数组。

public CopyOnWriteArrayList(Collection<? extends E> c) {
    Object[] elements = c.toArray();
    this.capacity = ArraysSupport.arrayLength(elements);
    myData = ArraysSupport.newArray(E.class, capacity);
    System.arraycopy(elements, 0, myData, 0, elements.length);
    size = elements.length;
}
  1. 获取元素

get方法根据索引获取数组中指定位置的元素。由于CopyOnWriteArrayList是线程安全的,因此在获取元素时不需要加锁。

public E get(int index) {
    if (index < 0 || index >= size) {
        throw new IndexOutOfBoundsException("Index: " + index + ", Size " + size);
    }
    return myData[index];
}
  1. 修改元素

set方法将指定索引位置的元素替换为新元素。它首先会检查索引的有效性,然后将当前索引位置的元素替换为新元素。与get方法一样,set方法也不需要加锁,因为它会在对数组进行修改时复制一份新的数组。

public E set(int index, E element) {
    if (index < 0 || index >= size) {
        throw new IndexOutOfBoundsException("Index: " + index + ", Size " + size);
    }
    E oldValue = myData[index];
    myData[index] = element;
    return oldValue;
}
  1. 添加元素

CopyOnWriteArrayList中,添加元素的主要方法是add(E e)。以下是该方法的大致源码解析:

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        // 确保数组容量足够
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 在新数组的最后位置添加元素
        newElements[len] = e;
        // 将新数组设置为当前数组
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

解析:

  • 首先,该方法获取了CopyOnWriteArrayList的内部锁,以确保线程安全。
  • 接着,它获取当前的数组,并计算其长度。
  • 使用Arrays.copyOf()方法创建一个新的数组,其容量比原始数组多1。这样做是为了容纳新添加的元素。
  • 在新数组的最后一个位置添加元素。
  • 最后,使用setArray()方法将新数组设置为当前的数组。
  • 无论操作是否成功,最后都要释放锁。

值得注意的是,每次对CopyOnWriteArrayList进行修改(如添加、删除元素)时,它都会创建一个新的数组。这种“写时复制”的策略确保了线程安全,但也意味着在频繁修改的情况下,可能会引起内存和性能上的问题。因此,CopyOnWriteArrayList最适用于读操作远多于写操作的场景。

  1. 删除元素

remove方法从集合中移除指定的元素。它会遍历数组,找到要删除的元素,并将其从数组中移除。然后,它会创建一个新的数组,将原始数组中剩余的元素复制到新数组中,并将新数组设置为当前数组。与add方法一样,remove方法也只需要在扩容时同步一次即可。

public E remove(int index) {
    final Object[] elements;
    final int length;
    elements = myData;
    length = size;
    if (index < 0 || index >= length) {
        throw new IndexOutOfBoundsException("Index: " + index + ", Size " + size);
    }
    // not inlined: HotSpot inlines only if the condition is false (it is not always true)
    E oldValue = (E) elements[index];
    int numMoved = length - index - 1;
    if (numMoved == 0) {
        // nothing to move, so just null out the removed element and return
        elements[index] = null;
    } else {
        // shift all elements down one position to fill the gap left by the removed element
        System.arraycopy(elements, index + 1, elements, index, numMoved);
    }
    // decrement size and clear the last element (which is now冗余)
    size--;
    elements[length - 1] = null;
    return oldValue;
}
  1. 迭代器

CopyOnWriteArrayList还提供了一个迭代器,用于遍历集合中的元素。由于CopyOnWriteArrayList是线程安全的,因此在迭代过程中不需要加锁。但是,如果在迭代过程中修改了集合,那么迭代器可能不会反映这些更改。因此,迭代器只能保证在创建时集合的一致性。

  1. 并发性能

CopyOnWriteArrayList采用写时复制的策略来保证线程安全。这种策略在高并发的情况下可能会导致频繁的复制操作,消耗一定的系统资源。但是,如果读操作的频率远远高于写操作的频率,那么CopyOnWriteArrayList可以提供较好的并发性能和较高的读操作吞吐量。此外,由于CopyOnWriteArrayList在修改集合时不需要加锁,因此它可以避免死锁和其他线程同步问题。

总的来说,CopyOnWriteArrayList适用于读操作远多于写操作的场景,它提供了一种线程安全的解决方案,使得在并发环境下也能够保证数据的一致性和可靠性。同时,我们也需要注意在使用CopyOnWriteArrayList时需要考虑其并发性能和适用场景。

在这里插入图片描述

CopyOnWriteArrayList使用注意点

使用CopyOnWriteArrayList时,需要注意以下几点:

  1. 写同步,读非同步:多个线程对CopyOnWriteArrayList进行写操作是线程同步的,因为内部使用了可重入锁,并且在进行修改时,内部先拷贝了一份数据源,再进行操作后,将原数据覆盖,解锁。但是读操作是非线程同步的,如果在for循环中使用下标的方式去读取数据,可能报错ArrayIndexOutOfBoundsException
  2. 内存占用问题:因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象。
  3. 数据一致性问题:CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。因为复制和操作元素需要一点儿时间,所以会有延迟。如果希望写入的数据马上能读到,要求数据强一致性的话,请不要使用CopyOnWrite容器。
  4. 迭代器的使用CopyOnWriteArrayList的迭代器实现了ListIterator接口,但是add()set()remove()方法都直接抛出了UnsupportedOperationException异常。所以应该避免使用迭代器的这几个方法。

请注意,CopyOnWriteArrayList适用于读操作远多于写操作的场景。如果写操作非常频繁,那么可能会引起内存和性能上的问题。在选择是否使用它时,需要根据具体的应用场景进行考虑。

CopyOnWriteArrayList存在的性能问题

CopyOnWriteArrayList的性能问题主要集中在以下几个方面:

  1. 写操作开销大:每次对列表进行修改操作(如add、set等),CopyOnWriteArrayList都会复制一份新的数据数组,这对内存和CPU都是较大的开销。如果写操作非常频繁,那么可能会引起内存占用过高和GC频繁,从而影响性能。
  2. 读操作可能不是实时的:由于写操作的复制机制,读操作可能不会立即看到最新的写入数据,这会导致数据的一致性问题。如果应用需要强一致性,那么CopyOnWriteArrayList可能不是一个好的选择。
  3. 迭代器操作可能抛出异常:如前所述,CopyOnWriteArrayList的迭代器不支持add、set和remove操作,如果尝试使用这些方法,会抛出UnsupportedOperationException异常。这可能会在使用迭代器进行遍历操作时引发问题。
  4. 不适合大量数据:由于写操作需要复制整个数据数组,如果列表中包含大量数据,那么写操作的开销会非常大。这种情况下,其他线程安全的列表实现(如ConcurrentLinkedQueueBlockingQueue)可能是更好的选择。

CopyOnWriteArrayList适用于读多写少的场景,且数据一致性要求不那么严格的情况。在使用时,需要根据应用的具体需求进行权衡和选择。

在这里插入图片描述

CopyOnWriteArrayList 使用实例

基本应用实例

下面是一个简单的Java代码实例,它演示了如何使用CopyOnWriteArrayList

import java.util.concurrent.CopyOnWriteArrayList;

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

        // 添加元素
        list.add("Hello");
        list.add("World");
        list.add("Java");

        // 输出列表中的元素
        for (String str : list) {
            System.out.println(str);
        }

        // 移除元素
        list.remove("World");

        // 输出列表中的元素
        for (String str : list) {
            System.out.println(str);
        }
    }
}

在这个例子中,我们创建了一个CopyOnWriteArrayList对象,并向其中添加了三个字符串元素。然后,我们使用一个简单的for-each循环遍历列表并输出其中的元素。接着,我们移除了一个元素,并再次遍历列表并输出剩余的元素。这个例子展示了CopyOnWriteArrayList的基本用法和特点。

并发应用实例

在并发环境中,CopyOnWriteArrayList的一个典型应用实例是实现一个线程安全的日志记录器。下面是一个示例代码,它使用了CopyOnWriteArrayList来存储日志条目,并确保在多线程环境下对日志的读取和写入操作都是安全的。

import java.util.concurrent.CopyOnWriteArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ThreadSafeLogger {
    private final CopyOnWriteArrayList<String> logEntries;
    private static final Logger LOGGER = Logger.getLogger(ThreadSafeLogger.class.getName());

    public ThreadSafeLogger() {
        logEntries = new CopyOnWriteArrayList<>();
    }

    public void log(String message) {
        logEntries.add(message);
        LOGGER.log(Level.INFO, message);
    }

    public void log(Exception ex) {
        logEntries.add(ex.getMessage());
        LOGGER.log(Level.SEVERE, ex.getMessage(), ex);
    }

    public void printLog() {
        for (String entry : logEntries) {
            System.out.println(entry);
        }
    }
}

在这个示例中,ThreadSafeLogger类使用CopyOnWriteArrayList来存储日志条目。log()方法用于将消息和异常添加到日志列表中,并使用Java的内置日志记录器(Logger)将消息记录到标准输出。printLog()方法遍历日志列表并打印所有条目。由于logEntries列表是线程安全的,因此可以在多线程环境中安全地添加、读取和打印日志条目。

在这里插入图片描述

拓展

写时复制

写时复制(Copy-On-Write,简称COW)是一种用于处理数据的计算机技术,其基本思想是当需要修改数据时,先将数据复制一份,然后在复制的数据上进行修改,这样原数据不会被改变,从而保证了数据的一致性和安全性。这种技术主要应用于并发环境,以避免多个线程或进程同时修改同一份数据而引发的问题。

在Java的CopyOnWriteArrayList中,写时复制技术被用来实现线程安全。当对列表进行修改操作(如add、set等)时,CopyOnWriteArrayList会先复制一份当前的数据数组,然后在复制的数据上进行修改,最后再将修改后的数据数组替换掉原来的数据数组。这样可以保证在进行写操作的同时,读操作可以无锁地访问原来的数据数组,从而实现线程安全。

写时复制技术的优点是可以实现高效的并发读写操作,因为读操作不需要加锁,可以并发进行。但是,写操作的开销比较大,因为每次写操作都需要复制一份数据,这会消耗较多的内存和CPU资源。因此,写时复制技术适用于读多写少的场景,如果写操作非常频繁,那么可能会影响性能。

需要注意的是,写时复制技术并不能完全保证数据的一致性。因为复制和操作元素需要一定的时间,所以可能会出现延迟,导致读操作不能立即看到最新的写入数据。因此,如果应用需要强一致性,那么写时复制技术可能不是一个好的选择。

写时复制技术的优点包括:

  • 如果调用者没有修改该资源,就不会有副本被建立,因此多个调用者只是读取操作可以共享同一份资源。

  • 写时复制可以减少不必要的资源分配。如fork进程时,并不是所有的页面都需要复制,父进程的代码段和只读数据段都不被允许修改,所以无需复制。

  • 当实体有需要对资源进行修改时才真正为实体分配私有资源,减少了分配和复制大量资源带来的延时。写时复制技术是一种很重要的优化手段,核心是懒惰处理实体资源请求,在多个实体资源之间只是共享资源,起初并不真正实现资源复制,只有当实体有需要对资源进行修改时才真正为实体分配私有资源。

总的来说,写时复制技术的优点主要是减少资源占用和提高效率。
在这里插入图片描述

ConcurrentLinkedDeque详解-Deque接口链表实现方案

ArrayDeque详解-Deque接口数组实现方案

LinkedList详解-Deque接口链表实现方案

Java中Deque接口方法解析

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/218964.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Uber Go 语言编码规范

uber-go/guide 的中文翻译 English 文档链接 Uber Go 语言编码规范 Uber 是一家美国硅谷的科技公司&#xff0c;也是 Go 语言的早期 adopter。其开源了很多 golang 项目&#xff0c;诸如被 Gopher 圈熟知的 zap、jaeger 等。2018 年年末 Uber 将内部的 Go 风格规范 开源到 G…

每天一点python——day85

#每天一点Python——85 #python常见的异常类型&#xff1a; #如图&#xff1a; #①数学运算异常【由于会报错&#xff0c;我直接全部注释掉了】 print(10/0) 输出&#xff1a;ZeroDivisionError: division by zero#②索引错误list1[1,2,3,4] print(list1[5])#找索引为4的元素 输…

谈一谈内存池

文章目录 一&#xff0c;什么是内存池二&#xff0c;进程地址空间中是如何解决内存碎片问题的三&#xff0c;malloc的实现原理四&#xff0c;STL中空间配置器的实现原理五&#xff0c;高并发内存池该内存池的优势在哪里内存池的设计框架内存申请流程ThreadCache层CentreCache层…

优酷新国风动漫《师兄啊师兄 第二季》强势定档 看李长寿稳健归来!

看新国风&#xff0c;上优酷动漫&#xff01;由优酷出品&#xff0c;玄机科技制作&#xff0c;改编自阅文集团旗下起点读书小说《我师兄实在太稳健了》&#xff08;作者&#xff1a;言归正传&#xff09;的修仙喜剧动画《师兄啊师兄》第二季《海神扬名篇》今日正式官宣定档&…

【Docker】从零开始:13.Docker安装tomcat

Docker】从零开始&#xff1a;13.Docker安装Tomcat 下载Tomcat镜像启动Tomcat镜像新版本Tomcat修改访问Tomact首页 下载Tomcat镜像 [rootdocker ~]# docker pull tomcat Using default tag: latest latest: Pulling from library/tomcat 0e29546d541c: Pull complete 9b829c7…

培训机构一定要做好网络安全措施,确保学员信息安全!

生活中大家对于培训机构一定很熟悉&#xff0c;因为从小到大都可以参加各种培训机构。参加培训机构时候&#xff0c;往往会登记各种信息&#xff0c;培训机构会记录存储。所以为了确保学员个人隐私安全&#xff0c;保障学员信息安全&#xff0c;培训机构一定要做好网络安全措施…

【设计模式】单例模式代码设计

目录 单例模式简介饿汉单例模式懒汉单例模式线程安全的懒汉单例模式 橙色 详细可参考该篇文章&#xff1a;C设计模式 - 单例模式 单例模式简介 单例模式指的是&#xff0c;无论怎么获取&#xff0c;永远只能得到该类类型的唯一一个实例对象&#xff0c;那么设计一个单例就必须…

RH850P1X芯片学习笔记-Pin Functions

文章目录 Pin Connection Diagrams术语定义 Pin ListPort OverviewIntroductionFunctional OverviewPort CategoryOperation Mode运行模式 Port Function寄存器地址映射 Port寄存器描述Pn/JP0 — Port RegisterPPRn/JPPR0 — Port Pin Read RegisterPMn/JPM0 — Port Mode Regi…

采集伪原创洗稿,实现文章创作的方法

各位写手小伙伴们&#xff0c;今天要和大家分享一些关于伪原创的方法和经验&#xff0c;希望这些建议能够在你们的写作之旅中派上用场。 首先我们需要明确一下&#xff0c;伪原创并不是鼓励抄袭&#xff0c;而是一种在保留原文核心思想的同时&#xff0c;通过巧妙的方式改写&a…

2024 年甘肃省职业院校技能大赛中职组 电子与信息类“网络安全”赛项竞赛样题-A

2024 年甘肃省职业院校技能大赛中职组 电子与信息类“网络安全”赛项竞赛样题-A 目录 2024 年甘肃省职业院校技能大赛中职组 电子与信息类“网络安全”赛项竞赛样题-A 需要环境或者解析可以私信 &#xff08;二&#xff09;A 模块基础设施设置/安全加固&#xff08;200 分&…

Footprint Analytics x Future3 万字研报:AI 与 Web3 数据行业融合的现状、竞争格局与未来机遇探析(上)

GPT的横空出世将全球的目光吸引至大语言模型&#xff0c;各行各业都尝试着利用这个“黑科技”提高工作效率&#xff0c;加速行业发展。Future3 Campus携手Footprint Analytics共同深入研究AI与Web3结合的无限可能&#xff0c;联合发布了《AI与Web3数据行业融合现状、竞争格局与…

操作系统原理-作业二-进程调度与死锁

1.设某系统中有四个进程 P1 、 P2 、 P3 和 P4 &#xff0c;它们的到达时刻依次为 0ms 、 1ms 、 2ms 、 3ms &#xff0c;估计运行时间分别为 6ms 、 1ms 、 8ms 、 4ms &#xff0c;若系统采用基于时间片轮转的三 级反馈队列调度算法进行调度&#xff0c;其中第一级队…

深度解析大模型背后的知识储存与提取:背诵不等于理解/MongoDB发布生成式AI新功能,大幅提高开发者工作效率和体验|魔法半周报

我有魔法✨为你劈开信息大海❗ 高效获取AIGC的热门事件&#x1f525;&#xff0c;更新AIGC的最新动态&#xff0c;生成相应的魔法简报&#xff0c;节省阅读时间&#x1f47b; &#x1f525;资讯预览 Mistral AI发布开源语言模型Mistral 7B&#xff0c;性能超越规模更大的Llama…

机器学习实验四:贝叶斯分类器

系列文章目录 机器学习实验一&#xff1a;线性回归机器学习实验二&#xff1a;决策树模型机器学习实验三&#xff1a;支持向量机模型机器学习实验四&#xff1a;贝叶斯分类器机器学习实验五&#xff1a;集成学习机器学习实验六&#xff1a;聚类 文章目录 系列文章目录一、实验…

计算机网络之IP篇

目录 一、IP 的基本认识 二、DNS 三、ARP 四、DHCP 五、NAT 六、ICMP 七、IGMP 七、ping 的工作原理 ping-----查询报文的使用 traceroute —— 差错报文类型的使用 八、断网了还能 ping 通 127.0.0.1 吗&#xff1f; 8.1、什么是 127.0.0.1 &#xff1f; 8.2、为…

11.10Redis基础

一.安装,启动,操作 二.远程连接 三.官方文档 https://redis.io/commands/ 四.Redis的数据类型 1.String 2.Hash(注意: value是字典类型) 3.List(类似于队列) 4.Set(无序) 5. ZSet(有序) 五.持久化 六.分布 七.SpringBoot集成Redis 1.添加依赖 2. 配置Redis 3.操作Redis(Serv…

如何选择适合自己的成品短视频app源码?

在当今数字化社会&#xff0c;短视频成为了人们日常生活中不可或缺的一部分。对于想要投身这一领域的创业者来说&#xff0c;选择适合自己的成品短视频app源码显得至关重要。在这篇文章中&#xff0c;我将为您提供一些建议&#xff0c;帮助您在众多选择中找到最合适的短视频app…

红警For Mac(RAM芯片可玩)

1、文件损坏解决版本&#xff01; 执行以下命令&#xff0c;&#xff08;注意&#xff1a;命令2应用路径根据实际情况修改&#xff09; sudo spctl --master-disable sudo xattr -r -d com.apple.quarantine /Applications/红警2尤里复仇M芯片.app2、新系统14&#xff0c;第一…

【3】密评-物理和环境安全测评

0x01 依据 GB/T 39786 -2021《信息安全技术 信息系统密码应用基本要求》针对等保三级系统要求&#xff1a; 物理和环境层面&#xff1a; a&#xff09;宜采用密码技术进行物理访问身份鉴别,保证重要区域进入人员身份的真实性&#xff1b; b&#xff09;宜采用密码技术保证电子门…

Centos7.4安装nginx1.24.0_安装详细步骤---Linux工作笔记066

以前安装的太模糊了,干脆重新写一个: 1.首先下载对应的nginx-1.24.0.tar.gz安装文件 2.然后: 去执行命令 安装依赖 yum install -y gcc yum install -y pcre pcre-devel yum install -y zlib zlib-devel yum install -y openssl openssl-devel 3.然后:去解压 tar -zxvf ngi…