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 returnelements[index] = null;} else {// shift all elements down one position to fill the gap left by the removed elementSystem.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.mzph.cn/news/200038.shtml

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

相关文章

企业网络安全守护者:EventLog Analyzer日志审计系统

在当今数字时代&#xff0c;企业网络不仅仅是业务运营的核心&#xff0c;也成为各种潜在威胁的目标。为了保障企业的网络安全&#xff0c;日志审计系统成为了不可或缺的一环。其中&#xff0c;ManageEngine的EventLog Analyzer作为一款强大而全面的日志管理与审计解决方案&…

【第三方】微信登录

目录 前言小程序登录步骤说明前端效果涉及到的接口登录凭证&#xff1a;wx.login获取用户信息&#xff1a;wx.getUserInfo 后端涉及到接口小程序登录 代码展示 微信扫码登录 前言 微信官方文档&#xff0c;需要对接哪个模块就从哪里进入。 由于本次我们需要的是小程序的登录。…

有趣的代码——有故事背景的程序设计3

这篇文章再和大家分享一些有“背景”的程序设计&#xff0c;希望能够让大家学到知识的同时&#xff0c;对编程学习更感兴趣&#xff0c;更能在这条路上坚定地走下去。 目录 1.幻方问题 2.用函数打印九九乘法表 3.鸡兔同笼问题 4.字数统计 5.简单选择排序 1.幻方问题 幻方又…

【无标题】什么是UL9540测试,UL9540:2023版本增加哪些测试项目

什么是UL9540测试&#xff0c;UL9540:2023版本增加哪些测试项目 UL 9540是美国安全实验室&#xff08;Underwriters Laboratories&#xff09;发布的标准&#xff0c;名称为"UL 9540: Energy Storage Systems and Equipment"&#xff0c;翻译为中文为"能量存储…

【JavaWeb】前端工程化(VUE3)

前端工程化&#xff08;VUE3&#xff09; 文章目录 前端工程化&#xff08;VUE3&#xff09;一、概述二、ECMA6Script2.1 es6的变量和模板字符串2.2 es6的解构表达式2.3 es6的箭头函数2.4 rest和spread2.5 es6的对象创建和拷贝2.6 es6的模块化处理 三、前端工程化环境搭建3.1 N…

数据分析师的学习之路-pandas篇(7)

继续接上篇&#xff0c;这次学习下透视表、线性回归还有根据条件上颜色。 3.9 透视表 在excel里也经常用到透视表来构建想要的列的组合来形成一个新的表&#xff0c;在pandas里也能做。 举例数据是这样的&#xff1a; 是各种类产品的订单数据&#xff0c;现在想做一个透视表&…

electron 应用图标修改

修改窗口图标 更换Electron应用程序的桌面图标 准备好你想要作为图标的图片文件&#xff0c;可以是PNG格式安装一个可以转换图片格式为ICO的工具&#xff0c;例如在线转换工具“在线转换icon图标工具”。将你的PNG图片文件上传并转换为ICO格式将转换得到的ICO文件放到你的El…

synxflow 安装环境

介绍&#xff1a; 该软件可以动态模拟洪水淹没&#xff0c;滑坡跳动和泥石流使用多个cuda支持的gpu。它还提供了一个用户友好但多功能的Python界面&#xff0c;可以完全集成到数据科学工作流程中&#xff0c;旨在简化和加速危害风险评估任务。 这个包我从网上找到的资源特别特…

GAN:WGAN-DIV

论文&#xff1a;https://arxiv.org/pdf/1712.01026.pdf 代码&#xff1a; 发表&#xff1a;2018 摘要 在计算机视觉的许多领域中&#xff0c;生成对抗性网络已经取得了巨大的成功&#xff0c;其中WGANs系列被认为是最先进的&#xff0c;主要是由于其理论贡献和竞争的定性表…

11、信息打点——红队工具篇FofaQuakeSuize水泽Arl灯塔

网络空间测绘引擎 Fofa Quake shodan Zoomeye 主要搜关联资产、特征资产、资产信息&#xff08;在测绘引擎上直接搜IP&#xff0c;它会显示所有与该域名有关的信息。&#xff09; fofa和Quake测绘引擎集成化工具&#xff1a;Finger 自动化信息收集项目 ARL灯塔 Suize水泽 …

洗地机好用吗?口碑好的洗地机有哪些?

自从洗地机开始引入市场以来&#xff0c;它一直受到人们的关注。它在解放家庭清洁劳动力和提供快速方便的清洁方面表现出色&#xff0c;超越了多年来传统的拖把清洁方式。越来越多的人选择使用洗地机来完成家庭清洁任务。如果你也对洗地机产生了浓厚的兴趣&#xff0c;并想购买…

在 AlmaLinux 9.2 上安装Oracle Database 23c

在 AlmaLinux 9.2 上安装Oracle Database 23c 1. 安装 Oracle Database 23c2. 连接 Oracle Database 23c3. 重启启动后&#xff0c;手动启动数据库4. 重启启动后&#xff0c;手动启动 Listener5. 手动启动 Pluggable Database6. 自动启动 Pluggable Database7. 设置开机启动数据…

【面试】Java最新面试题资深开发-JVM第一弹

问题一&#xff1a;Java中的垃圾回收机制 在Java中&#xff0c;垃圾回收是如何工作的&#xff0c;可以简要描述一下垃圾回收的算法有哪些吗&#xff1f; 在Java中&#xff0c;垃圾回收是一种自动管理内存的机制&#xff0c;它负责识别不再被程序引用的对象并释放其占用的内存…

Linux(11):Linux 账号管理与 ACL 权限设定

Linux 的账号与群组 每个登入的使用者至少都会取得两个 ID&#xff0c;一个是使用者 ID(User ID &#xff0c;简称UID)、一个是群组ID (Group ID &#xff0c;简称GID)。 Linux系统上面的用户如果需要登入主机以取得 shell 的环境来工作时&#xff0c;他需要如何进行呢? 首先…

测绘资质测绘设备检定、校准管理制度

测绘设备检定、校准管理制度 建立健全测绘仪器设备检定、校准管理制度&#xff0c;明确测绘仪器设备的检定、校准、日常管理等要求

Unity环境配置并解决visual studio 不能智能代码提示Unity代码问题(一)

1、请先安装好unity和Visual Studio 2019 2、Visual Studio需要安装如图&#xff08;2019才会有那个移动的可以勾选&#xff09; 3、Unity配置 file->build setting windows->package manager 安装如下图 edit->preferences 3、创建c#脚本 如果还是没能智能提…

Centos7上安装Redis

第一步&#xff1a;安装Redis依赖 yum install -y gcc tcl //需要使用管理员权限第二步&#xff1a;下载上传安装包并解压 下载地址redis中文官网 上传成功后解压 输入tar -zxvf &#xff08;redis版本&#xff09;,即可解压成功 进入redis目录&#xff0c;运行编译命令&am…

安全AI系统开发指南

执行摘要 本文件建议为使用人工智能&#xff08;AI&#xff09;的任何系统的提供商提供指导方针&#xff0c;无论这些系统是从头开始创建的&#xff0c;还是建立在他人提供的工具和服务之上的。实施这些指导方针将有助于提供商构建按预期运行、在需要时可用的人工智能系统&…

CTF特训日记day(4-6)

来复现一下2022QWB决赛的RDP题目 这两天腰疼去了趟医院 题目要求我们攻击XRDP程序&#xff0c;从而达到本地提权的效果。 首先观察XRDP程序的版本信息 rootRDP:/home/rdp/Desktop# xrdp-sesman -version xrdp-sesman 0.9.18The xrdp session managerCopyright (C) 2004-2020…

【eNSP实践】eNSP实战篇(2)之简单实现交换机与主机的配置(图文详解)

目录 写在前面涉及知识1、交换机实验1.1 实验条件1.2 实验步骤A、打开eNSP软件&#xff0c;创建拓扑B、搭建主机与交换机连线C、配置交换机和主机D、验证不同网段设备可通性 1.3 通过交换机查看MAC地址 写在最后 写在前面 其实前面文章我有介绍关于路由器的使用&#xff0c;但…