CopyOnWriteArrayList源码分析

其中唯一的线程安全 List 实现就是 CopyOnWriteArrayList。

特点

由于读取操作不会对原有数据进行修改,因此,对于每次读取都进行加锁其实是一种资源浪费。相比之下,我们应该允许多个线程同时访问 List 的内部数据,毕竟对于读取操作来说是安全的。
这种思路与 ReentrantReadWriteLock 读写锁的设计思想非常类似,即读读不互斥、读写互斥、写写互斥(只有读读不互斥)。
CopyOnWriteArrayList 更进一步地实现了这一思想。为了将读操作性能发挥到极致,CopyOnWriteArrayList 中的读取操作是完全无需加锁的。更加厉害的是,写入操作也不会阻塞读取操作,只有写写才会互斥。这样一来,读操作的性能就可以大幅度提升。

CopyOnWriteArrayList 线程安全的核心在于其采用了 写时复制(Copy-On-Write) 的策略

思想

写入时复制(英语:Copy-on-write,简称 COW)是一种计算机程序设计领域的优化策略。其核心思想是,如果有多个调用者(callers)同时请求相同资源(如内存或磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者试图修改资源的内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的。此作法主要的优点是如果调用者没有修改该资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。

当需要修改( add,set、remove 等操作) CopyOnWriteArrayList 的内容时,不会直接修改原数组,而是会先创建底层数组的副本,对副本数组进行修改,修改完之后再将修改后的数组赋值回去,这样就可以保证写操作不会影响读操作了。写时复制机制非常适合读多写少的并发场景,能够极大地提高系统的并发性能。

写时复制缺点

  1. 内存占用:每次写操作都需要复制一份原始数据,会占用额外的内存空间,在数据量比较大的情况下,可能会导致内存资源不足。
  2. 写操作开销:每一次写操作都需要复制一份原始数据,然后再进行修改和替换,所以写操作的开销相对较大,在写入比较频繁的场景下,性能可能会受到影响。
  3. 数据一致性问题:修改操作不会立即反映到最终结果中,还需要等待复制完成,这可能会导致一定的数据一致性问题。

结构

在这里插入图片描述

add

// 插入元素到 CopyOnWriteArrayList 的尾部
public boolean add(E e) {final ReentrantLock lock = this.lock;// 加锁lock.lock();try {// 获取原来的数组Object[] elements = getArray();// 原来数组的长度int len = elements.length;// 创建一个长度+1的新数组,并将原来数组的元素复制给新数组Object[] newElements = Arrays.copyOf(elements, len + 1);// 元素放在新数组末尾newElements[len] = e;// array指向新数组setArray(newElements);return true;} finally {// 解锁lock.unlock();}
}
  • add方法内部用到了 ReentrantLock 加锁,保证了同步,避免了多线程写的时候会复制出多个副本出来。
  • CopyOnWriteArrayList 通过复制底层数组的方式实现写操作,即先创建一个新的数组来容纳新添加的元素,然后在新数组中进行写操作,最后将新数组赋值给底层数组的引用,替换掉旧的数组。采用了 写时复制(Copy-On-Write) 的策略。
  • 每次写操作都需要通过 Arrays.copyOf 复制底层数组,时间复杂度是 O(n) 的,且会占用额外的内存空间。因此,CopyOnWriteArrayList 适用于读多写少的场景。
  • CopyOnWriteArrayList 中并没有类似于 ArrayList 的 grow() 方法扩容的操作。

读取元素

CopyOnWriteArrayList 的读取操作是基于内部数组 array 并没有发生实际的修改,因此在读取操作时不需要进行同步控制和锁操作,可以保证数据的安全性。这种机制下,多个线程可以同时读取列表中的元素。

// 底层数组,只能通过getArray和setArray方法访问
private transient volatile Object[] array;public E get(int index) {return get(getArray(), index);
}final Object[] getArray() {return array;
}private E get(Object[] a, int index) {return (E) a[index];
}

get方法是弱一致性的,在某些情况下可能读到旧的元素值。

获取列表中元素的个数

CopyOnWriteArrayList中的array数组每次复制都刚好能够容纳下所有元素,并不像ArrayList那样会预留一定的空间。因此,CopyOnWriteArrayList中并没有size属性CopyOnWriteArrayList的底层数组的长度就是元素个数,因此size()方法只要返回数组长度就可以了。

删除

public E remove(int index) {// 获取可重入锁final ReentrantLock lock = this.lock;// 加锁lock.lock();try {//获取当前array数组Object[] elements = getArray();// 获取当前array长度int len = elements.length;//获取指定索引的元素(旧值)E oldValue = get(elements, index);int numMoved = len - index - 1;// 判断删除的是否是最后一个元素if (numMoved == 0)// 如果删除的是最后一个元素,直接复制该元素前的所有元素到新的数组setArray(Arrays.copyOf(elements, len - 1));else {// 分段复制,将index前的元素和index+1后的元素复制到新数组// 新数组长度为旧数组长度-1Object[] newElements = new Object[len - 1];System.arraycopy(elements, 0, newElements, 0, index);System.arraycopy(elements, index + 1, newElements, index,numMoved);//将新数组赋值给array引用setArray(newElements);}return oldValue;} finally {// 解锁lock.unlock();}
}

判断元素是否存在

// 判断是否包含指定元素
public boolean contains(Object o) {//获取当前array数组Object[] elements = getArray();//调用index尝试查找指定元素,如果返回值大于等于0,则返回true,否则返回falsereturn indexOf(o, elements, 0, elements.length) >= 0;
}// 判断是否保证指定集合的全部元素
public boolean containsAll(Collection<?> c) {//获取当前array数组Object[] elements = getArray();//获取数组长度int len = elements.length;//遍历指定集合for (Object e : c) {//循环调用indexOf方法判断,只要有一个没有包含就直接返回falseif (indexOf(e, elements, 0, len) < 0)return false;}//最后表示全部包含或者制定集合为空集合,那么返回truereturn true;
}private static int indexOf(Object o, Object[] elements,int index, int fence) {if (o == null) {for (int i = index; i < fence; i++)if (elements[i] == null)return i;} else {for (int i = index; i < fence; i++)if (o.equals(elements[i]))return i;}return -1;
}

作者声明

如有问题,欢迎指正!

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

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

相关文章

MeterSphere压测,出现HttpHostConnectException

现象&#xff1a;MeterSphere更换压力机后&#xff0c;压测出现出现HttpHostConnectException 解决方案&#xff1a; net.ipv4.tcp_tw_reuse默认是0或者2&#xff0c;更改为1 net.ipv4.tcp_tw_reuse&#xff0c;表示是否允许重新应用处于TIME-WAIT状态的socket用于新的TCP连…

时序分解 | MATLAB实现基于SSA奇异谱分析的信号分解分量可视化

时序分解 | MATLAB实现基于LMD局部均值分解的信号分解分量可视化 目录 时序分解 | MATLAB实现基于LMD局部均值分解的信号分解分量可视化效果一览基本介绍程序设计参考资料 效果一览 基本介绍 奇异谱分解奇异谱分析SSA 可直接替换txt数据运行 Matlab 1.包含3D分解效果图 频谱图等…

Linux UDP编程流程

文章目录 UDP编程流程UDP协议无连接的特点UDP协议数据报的特点 UDP编程流程 UDP 提供的是无连接、不可靠的、数据报服务。服务器端和客户端没有什么本质上的区别。编程流程如下&#xff1a; socket()用来创建套接字&#xff0c;使用 udp 协议时&#xff0c;选择数据报服务 SOC…

Spring基础(2w字---学习总结版)

目录 一、Spirng概括 1、什么是Spring 2、什么是容器 3、什么是IoC 4、模拟实现IoC 4.1、传统的对象创建开发 5、理解IoC容器 6、DI概括 二、创建Spring项目 1、创建spring项目 2、Bean对象 2.1、创建Bean对象 2.2、存储Bean对象&#xff08;将Bean对象注册到容器…

IMU+摄像头实现无标记运动捕捉

惯性传感和计算机视觉的进步为在临床和自然环境中获得精准数据带来了新可能。然而在临床应用时需要仔细地将传感器与身体对齐&#xff0c;这减慢了数据收集过程。 随着无标记运动捕捉的发展&#xff0c;研究者们提出了一个新的深度学习模型&#xff0c;利用来自视觉、惯性传感…

金和OA GetSqlData.aspx 远程命令执行漏洞

一、漏洞简介 金和OA协同办公管理系统C6软件共有20多个应用模块,160多个应用子模块,涉及的企业管理业务包括协同办公管理、人力资源管理、项目管理、客户关系管理、企业目标管理、费用管理等多个业务范围,从功能型的协同办公平台上升到管理型协同管理平台,并不断的更新完善…

每日刷题-5

目录 一、选择题 二、算法题 1、不要二 2、把字符串转换成整数 一、选择题 1、 解析&#xff1a;printf(格式化串&#xff0c;参数1&#xff0c;参数2,.….)&#xff0c;格式化串: printf第一个参数之后的参数要按照什么格式打印&#xff0c;比如%d--->按照整形方式打印&am…

2023备战秋招Java面试八股文合集

Java就业大环境仍然根基稳定&#xff0c;市场上有很多机会&#xff0c;技术好的人前景就好&#xff0c;就看你有多大本事了。小编得到了一份很不错的资源&#xff0c;建议大家可以认真地来看看以下的资料&#xff0c;来提升一下自己的核心竞争力&#xff0c;在面试中轻松应对面…

HTTPS协议

文章目录 HTTPS协议1. HTTPS协议介绍2. 概念准备2.1 什么是"加密"2.2 为什么要加密2.3 常见的加密方式(1) 对称加密(2) 非对称加密 2.4 数据摘要 && 数据指纹(1) 应用场景 3. HTTPS理解链3.1 方案 1 - 只使用对称加密3.2 方案 2 - 只使用非对称加密3.3 方案 …

SeaArt.ai: 海艺AI绘画艺术图片模型创作平台

【产品介绍】 • 名称 SeaArt.ai • 具体描述 SeaArt.ai是一个基于人工智能技术的AI绘画工具&#xff0c;它可以根据你的描述或者关键词来生成符合你想象的图片。你可以选择不同的模式来创建不同类型的图片&#xff0c;比如人物、风景、建筑、神话、自…

投稿指南【NO.12_8】【极易投中】核心期刊投稿(组合机床与自动化加工技术)

近期有不少同学咨询投稿期刊的问题&#xff0c;大部分院校的研究生都有发学术论文的要求&#xff0c;少部分要求高的甚至需要SCI或者多篇核心期刊论文才可以毕业&#xff0c;但是核心期刊要求论文质量高且审稿周期长&#xff0c;所以本博客梳理一些计算机特别是人工智能相关的期…

Swift 新并发模型中 isolated 和 nonisolated 关键字的含义看这篇就懂了!

概览 在 Swift 新 async/await 并发模型中&#xff0c;我们可以利用 Actor 来避免并发同步时的数据竞争&#xff0c;并从语义上简化代码。 Actor 伴随着两个独特关键字&#xff1a;isolated 和 nonisolated&#xff0c;弄懂它们的含义、合理合规的使用它们是完美实现同步的必…

SpringMVC文件的上传下载JRebel的使用

目录 前言 一、JRebel的使用 1.IDea内安装插件 2.激活 3.离线使用 使用JRebel的优势 二、文件上传与下载 1 .导入pom依赖 2.配置文件上传解析器 3.数据表 4.配置文件 5.前端jsp页面 6.controller层 7.测试结果 前言 当涉及到Web应用程序的开发时&…

只依赖OPENCV的工作服安全帽检测YOLOV8S

工地安全帽工作服检测Y8S&#xff0c;采用YOLOV8S训练模型&#xff0c;然后使用OPENCV的DNN调用&#xff0c;彻底拜托PYTORCH依赖&#xff0c;可以在C,PYTHON,ANDROID上跑。附件是C生成的效果测试&#xff08;只需解压将图片或者视频放入VIDEOS文件夹&#xff0c;文件夹没图片或…

【UE 材质】制作飘动的旗帜

效果 步骤 1. 首先在建模软件中创建一个平面&#xff0c;注意分段数一定要多 2. 在UE中创建一个材质&#xff0c;这里命名为“Mat_Flag” 打开“Mat_Flag”&#xff0c;先将旗帜纹理连接到基础颜色 先选中导入的模型然后点击根据选中的模型预览材质 创建如下节点可以看到此时模…

如何让报表会说话?学会这一招就可以了

Smartbi电子表格软件进化到全新阶段&#xff0c;这轮更新最能吸引人的地方是什么呢&#xff1f;跟随小麦一起来感受下。 1、新增语音播放特性&#xff0c;让报表会说话 Smartbi电子表格软件最新上线超级实用的新特性——语音播报&#xff0c;即让报表自己开口说话~ 设想你打开…

matplotlib绘图

&#x1f433; 我正在和鲸社区参加“商业数据分析训练营活动” https://www.heywhale.com/home/competition/6487de6649463ee38dbaf58b &#xff0c;以下是我的学习笔记&#xff1a; 学习主题&#xff1a;matplotlib绘图 日期&#xff1a;2023.9.13 关键概念/知识点&#xf…

TikTok的生态封闭:如何应对新的商业格局

TikTok&#xff08;抖音国际版&#xff09;已经成为全球范围内最受欢迎的社交媒体平台之一&#xff0c;拥有数以亿计的活跃用户。然而&#xff0c;最近TikTok宣布了一项重大战略调整&#xff0c;即开始构建生态封闭&#xff08;walled garden&#xff09;模式。 这一变化将对出…

bit、bin 、mcs文件区别

FPGA里面的可执行文件都涉及到 *.bit&#xff0c; *.mcs&#xff0c; *.bin 和 *.elf。 bit文件 bit 文件一般用于JTAG在线进行调试的时候&#xff0c;是把bit文件是烧写到FPGA中进行在线调试。 bin文件 bin 文件是二进制文件&#xff0c;按顺序只包含原始字节流&#xff0c…

批量剪辑视频:AI智剪技巧全掌握

在视频剪辑的工作中&#xff0c;我们常常需要处理大量的视频文件。传统的剪辑方式既耗时又费力&#xff0c;而如今&#xff0c;随着人工智能技术的发展&#xff0c;AI智剪成为了一种省时省力的视频剪辑方法。下面&#xff0c;我们就来详细了解一下如何使用固乔智剪软件进行批量…