CopyOnWriteArrayList怎么用

news2024/11/14 21:59:50

    • 什么是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接口方法解析

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023经典软件测试面试题

1、问&#xff1a;你在测试中发现了一个bug&#xff0c;但是开发经理认为这不是一个bug&#xff0c;你应该怎样解决&#xff1f; 首先&#xff0c;将问题提交到缺陷管理库里面进行备案。 然后&#xff0c;要获取判断的依据和标准&#xff1a; 根据需求说明书、产品说明、设计…

2024清理软件排名第一的是CCleaner

CCleaner2024版是一款专业好用的系统优化和隐私保护工具。CCleaner官方版主要用来清除Windows系统不再使用的垃圾文件和使用者的上网记录以空出硬盘容量&#xff0c;按工具同时注重保护用户隐私&#xff0c;被誉为“世界上最受欢迎的PC清洁剂”。 CCleaner下载如下&#xff1a…

【23真题】押题卷的漏网之鱼!

今天分享的是23年中国计量大学805的信号与系统试题及解析。第二大题的第1小题这类题&#xff01;太经典了&#xff0c;他那个相位图像&#xff0c;怎么看都是24真题的样子图片。但是我出的话&#xff0c;会把幅频特性从三角变为矩形&#xff0c;再加上个信号是否无失真的判断。…

JavaEE进阶学习:Spring Boot 配置文件

1.配置文件的作用 整个项目中所有重要的数据都是在配置文件中配置的&#xff0c;比如&#xff1a; 数据库的连接信息&#xff08;包含用户名和密码的设置&#xff09;&#xff1b;项目的启动端口&#xff1b;第三方系统的调用秘钥等信息&#xff1b;用于发现和定位问题的普通…

【Unity动画】Unity 动画播放的流程

本文以2D为案例&#xff0c;讲解Unity 播放动画的流程 准备和导入2D动画资源 外部导入序列帧生成的 Unity内部制作的 外部导入的3D动画 2.创建动画过程 打开时间轴Ctrl6 选中场景中的一个未来需要播放动画的物体 回到时间轴点击Create一个新动画片段 拖动2D动画资源放入…

Spark---Spark on Hive

1、Spark On Hive的配置 1&#xff09;、在Spark客户端配置Hive On Spark 在Spark客户端安装包下spark-2.3.1/conf中创建文件hive-site.xml&#xff1a; 配置hive的metastore路径 <configuration><property><name>hive.metastore.uris</name><v…

景联文科技解读《2023人工智能基础数据服务产业发展白皮书》,助力解决数据标注挑战

前段时间&#xff0c;国家工业信息安全发展研究中心发布《2023人工智能基础数据服务产业发展白皮书》&#xff08;以下简称“白皮书”&#xff09;。 《白皮书》指出&#xff0c;2022年&#xff0c;中国人工智能基础数据服务产业的市场规模为45亿元&#xff0c;预计今年将达到5…

Mybatis异常org.apache.ibatis.binding.BindingException: Parameter “xxx“ not found

问题1: 可能是 mybatis 的xml&#xff0c;对应的mapper接口缺少Param注解&#xff0c;或者Param注解的value与xml的不一致 切记只要参数不是一个集合类型向下图或者多个参数值就要加Param注解 问题2: mybatis的xml&#xff0c;存在多余的注释。注释中包含#{}、${}。注释掉的代…

游戏开发增笑-扣扣死-Editor的脚本属性自定义定制-还写的挺详细的,旧版本反而更好

2012年在官方论坛注册的一个号&#xff0c;居然被禁言了&#xff0c;不知道官方现在是什么辣鸡&#xff0c;算了&#xff0c;大人不记狗子过 ”后来提交问题给CEO了&#xff0c;结果CEO百忙之中居然回复了&#xff0c;也是很低调的一个人&#xff0c;毕竟做技术的有什么坏心思呢…

Leetcode周赛374补题(3 / 3) - EA专场

不愧是EA的题&#xff0c;我最爱的模拟人生……好难&#xff0c;呜呜 目录 1、找出峰值 - 暴力枚举 2、需要添加的硬币的最小数量 - 思维 贪心 3、统计完全子字符串 - 滑窗 分组循环 1、找出峰值 - 暴力枚举 2951. 找出峰值 class Solution {public List<Integer> …

Video Studio会声会影2024中文直装旗舰版

Corel Video Studio会声会影2024中文直装旗舰版是一款很流行的视频编辑处理软件&#xff0c;由于其简单易用&#xff0c;且功能不错&#xff0c;在国内拥有众多使用者&#xff0c;小编之前给大家分享过Corel Video Studio Ultimate会声会影2024旗舰版中文版&#xff0c;今天再为…

知识蒸馏的蒸馏损失方法代码总结(包括:基于logits的方法:KLDiv,dist,dkd等,基于中间层提示的方法:)

有两种知识蒸馏方法&#xff1a;一种利用教师模型的输出概率&#xff08;基于logits的方法&#xff09;[15,14,11]&#xff0c;另一种利用教师模型的中间表示&#xff08;基于提示的方法&#xff09;[12,13,18,17]。基于logits的方法利用教师的输出作为辅助信号来训练一个较小的…

免费的SEO外链发布工具,提升排名的利器

互联网已经成为信息传播和商业发展的重要平台。而对于拥有网站的个人、企业来说&#xff0c;如何让自己的网站在搜索引擎中脱颖而出&#xff1f;SEO&#xff08;Search Engine Optimization&#xff09;作为提高网站在搜索引擎中排名的关键手段. 什么是SEO外链&#xff1f; S…

C#,数值计算——计算实对称矩阵所有特征值和特征向量的雅可比(Jacobi)方法与源程序

1 文本格式 using System; namespace Legalsoft.Truffer { /// <summary> /// Computes all eigenvalues and eigenvectors of /// a real symmetric matrix by Jacobis method. /// </summary> public class Jacobi { private …

09、pytest多种调用方式

官方用例 # content of myivoke.py import sys import pytestclass MyPlugin:def pytest_sessionfinish(self):print("*** test run reporting finishing")if __name__ "__main__":sys.exit(pytest.main(["-qq"],plugins[MyPlugin()]))# conte…

Linux 输入输出重定向

Linux 系统默认的输入输出有3种类型&#xff0c;分别为标准输入、标准输出、错误输出&#xff0c;并且Linux 还为这几类设备分别分配了一个所谓的文件描述符&#xff0c;如下是他们之间的对应关系。 输入输出类型文件描述符系统中设备名通常对应的物理设备标准输入设备0/dev/s…

IntelliJ IDEA的下载安装配置步骤详解

引言 IntelliJ IDEA 是一款功能强大的集成开发环境&#xff0c;它具有许多优势&#xff0c;适用于各种开发过程。本文将介绍 IDEA 的主要优势&#xff0c;并提供详细的安装配置步骤。 介绍 IntelliJ IDEA&#xff08;以下简称 IDEA&#xff09;之所以被广泛使用&#xff0c;…

SpringBoot集成i18n(多语言)

配置文件 spring: messages: basename: il8n/messages # 配置国际化资源文件路径 fallback-to-system-locale: true # 是否使用系统默认的语言环境作为备选项 国际化配置 import org.springframework.context.annotation.Bean; import org.spr…

基于Eclipse+Mysql+Tomcat开发的 教学评价管理系统

基于EclipseMysqlTomcat开发的 教学评价管理系统 项目介绍&#x1f481;&#x1f3fb; 随着教育信息化的发展&#xff0c;教学评价管理系统已经成为了学校、教育机构等场所必不可少的一部分。本项目是基于EclipseMysqlTomcat开发的一套教学评价管理系统&#xff0c;旨在帮助教育…

成为AI产品经理——回归模型评估(MSE、RMSE、MAE、R方)

分类问题的评估是看实际类别和预测类别是否一致&#xff0c;它的评估指标主要有混淆矩阵、AUC、KS。回归问题的评估是看实际值和预测值是否一致&#xff0c;它的评估指标包括MAE、MSE、RMSE、R方。 如果我们预测第二天某支股票的价格&#xff0c;给一个模型 y1.5x&#xff0c;…