CopyOnWriteArrayList原理分析

1.简介

JDK1.5之前,由于那个版本尚未退出专门运行在并发环境下的集合,对于List类型,我明只能选择Vector或者Stack这种老古董,但是它们效率太低(全部的增删改方法均被synchronized修饰着,那个时候的synchronized由于还没有经过长期充分的优化,性能很低下):
Vector的部分源码
JDK1.5之后引入了java.utl.concurrent包,其中提供了很多适用于并发环境的集合,而在这其中,List的对应的唯一实现就是CopyOnWriteArrayList


2.基本原理

在大多数业务场景中,读操作往往是远大于写操作的,而VectorStack不管是读还是写都要进行加锁,对于读的情况加锁不仅是毫无意义的(读读状态下),而且还会大大降低了读的效率。因此,CopyOnWriteArrayList实现了读读、读写共存,写写互斥,从而大幅度提升读写性能。
CopyOnWriteArrayList是通过Copy-On-Write(写时复制,简称COW)这一技术实现了上面的目标。

写时复制:

当多个调用者需要访问同一份资源时,如果它们发出的都是读操作,那么它们将共享同一份原始资源,一旦有某个调用者发出写操作,系统将自动为其拷贝一份原始资源的副本供其使用,后续该调用者的写操作均发生在该资源副本上面,其他的资源调用者不受影响。
写时复制过程

具体实现思路为:

  • 对于增删改操作:先拷贝一份原始数组,然后在这个原始数组的副本上进行增删改操作,操作完再赋值回去。
  • 对于读操作:流程与普通读操作一致,但是与VectorStack不同,这个操作不加锁

写时复制的缺点:

  1. 内存占用:由于写操作需要触发资源拷贝,一旦原始资源大小较为庞大,则拷贝的过程可能会导致内存资源不足。
  2. 写操作开销大:写之前需要拷贝原始资源,然后修改后再替换掉原始资源,整个过程要比普通写操作开销大。
  3. 存在数据一致性问题:在写操作结束后 - 替换掉原始资源之前的这段时间内,可能会出现数据不一致的情况。

3.源码分析

3.1 类定义

CopyOnWriteArrayList的类定义(JDK17)")
CopyOnWriteArrayList实现了以下接口:

  • List:说明它属于链表的一种,操作与链表基本保持一致。
  • RandomAccess:支持随机访问/下标访问,也暗示了它的低层数据结构很有可能是数组!
  • Cloneable:通过重写clone方法实现了对象的深/浅拷贝。
  • Serializable:可序列化,即 可将该对象转换为字节流以实现持久化存储或网络传输。

CopyOnWriteArrayList的继承实现链(JDK17)")

3.2 初始化

CopyOnWriteArrayList的构造方法源码(JDK17)")
补充说明:setArray和getArray方法(JDK17)")
这里,我们仅先介绍一下中间的构造方法:

  1. 首先它先判断了传入的集合是否与它类型一致,都是CopyOnWriteArrayList,如果是的话,那直接通过getArray方法获取其内部的数组并赋值即可。
  2. 如果类型不一致,则调用集合的toArray方法获取其内部的元素集合转换后得到的数组,然后判断该数组的类型是否为Object[].class,如果是的话,则直接赋值;否则需要通过调用Arrays.copyOf方法,将该元素由原来的数组复制到新创建的Object数组中之后,再去将该新创建的Object数组赋值给elements字段。

这里说明一下,为什么调用toArray方法拿到集合元素构成的数组之后不直接赋值,并且elements字段是Object[]类型,不应该存在赋不了值的情况,这是为什么呢?
首先我们看到,这里的赋值都是将数组的引用直接赋值给elements字段的,而ArrayList的目标是尽可能地使内部的elements类型是Object[],哪怕它存储的元素不是Object类型。因为可能会存在这种情况,就是你指定ArrayList的泛型为类A,而初始化时,你传入了类A的子类B构成的集合,如果没有上面的操作的话,它是可以被成功复制给elements字段的,那后续如果我再往里面添加类A的对象时,就会报错。而集合的toArray方法没有被final修饰,是可以被重写的,而由方法重写的规则我们可知,我们完全可以在重写的toArray方法中返回一个泛型子类构成的数组!因此,上面的检查并转换的操作是很有必要的。
image.png

3.3 add

这里,由于CopyOnWriteArrayList中重载了多个add方法:
CopyOnWriteArrayList中的add相关的重载方法
所以这里以add(int index, E element)以小见大,进行讲解:
CopyOnWriteArrayList的add(int index, E element)方法源码(JDK17)方法源码(JDK17)")

执行流程

评估

  • 可以发现,我们是在获取锁之后再去执行添加操作,这根除了并发性问题。
  • 添加的基本过程是 拷贝数组 -> 添加元素到新数组 -> 将新数组重新赋值给elements,这正式写时复制策略的体现。
  • 由于添加过程涉及到数组的拷贝,因此空间复杂度为O(n)。
  • CopyOnWriteArrayList没有扩容操作,因此每次数组的扩容都是+1的形式,这意味着几乎每次add操作都要走一遍数组扩容的流程。尽管如此,但值得注意的是,CopyOnWriteArrayList数组的拷贝均调用的是系统基本的指令,因此性能依旧是杠杠的(前提是你的数据量不能过于庞大)。

3.4 get

CopyOnWriteArrayList的get(int index)方法源码(JDK17)方法源码(JDK17)")

执行流程

先获取到元素数组的引用,然后直接获取指定下标的元素。

评估

  • 读的过程可能会出现数据不一致的情况(写操作已在副本上执行完成,但副本尚未赋值给elements的期间)。
  • 读过程不加锁。

3.5 remove

CopyOnWriteArrayList的remove(int index)方法源码(JDK17)方法源码(JDK17)")

执行流程

与add过程几乎一模一样,无非就是数组拷贝的参数调整了一下。

计算需要移动的元素个数这里,之所以-1是因为len-index得到的是从index位置起到末尾的元素个数,也就是这里面包含了待删除的那个元素,因此需要-1排除掉它。

3.6 size

CopyOnWriteArrayList的size()方法源码(JDK17)方法源码(JDK17)")

评估

因为CopyOnWriteArrayList的使用的全过程中,其内部的Object数组的都是充盈的(从其构造函数以及后面的增删改操作中就可以看出),即不存在空隙,因此数组的长度即为元素的个数。

3.6 contains

CopyOnWriteArrayList的contains(Object o)方法源码(JDK17)方法源码(JDK17)")

执行流程

根据待检索元素是不是为null分情况进行全数组遍历。


参考文档

CopyOnWriteArrayList 源码分析

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

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

相关文章

【有手就行】使用你自己的声音做语音合成,CPU都能跑,亲测有效

此文介绍在百度飞桨上一个公开的案例,亲测有效。 厌倦了前篇一律的TTS音色了吗?打开短视频听来听去就是那几个声音,快来试试使用你自己的声音来做语音合成吧!本教程非常简单,只需要你能够上传自己的音频数据就可以(建议…

Helm安装kafka3.7.0无持久化(KRaft 模式集群)

文章目录 2.1 Chart包方式安装kafka集群 5.开始安装2.2 命令行方式安装kafka集群 搭建 Kafka-UI三、kafka集群测试3.1 方式一3.2 方式二 四、kafka集群扩容4.1 方式一4.2 方式二 五、kafka集群删除 参考文档 [Helm实践---安装kafka集群 - 知乎 (zhihu.com)](https://zhuanlan.…

离散数学--图论

目录 1.简单概念 2.握手定理 3.点割集 4.边割集 5.点连通度和边连通度 6.Dijstra算法&&最短路径 7.有向图的连通性 8.图的矩阵表示 9.欧拉图问题 10.哈密尔顿图 1.简单概念 (1)这个里面的完全图比较重要,完全图是例如k3,k5这…

vue项目报错:internal/modules/cjs/loader.js:892 throw err;

前言: vue项目中无法正常使用git,并报错情况。 报错信息: internal/modules/cjs/loader.js:892throw err;^ Error: Cannot find module D:\project\sd_wh_yth_front\node_modules\yorkie\src\runner.js 报错处理: npm install y…

局部直方图均衡化去雾算法

目录 1. 引言 2. 算法流程 3. 代码 4. 去雾效果 1. 引言 局部直方图算法是一种基于块的图像去雾方法,它将图像分割为若干个块,并在每个块内计算块的局部直方图。通过对各个块的直方图进行分析和处理,该算法能够更好地适应图像中不同区域的…

九宫格转圈圈抽奖活动,有加速,减速效果

在线访问demo和代码在底部 代码&#xff0c;复制就可以跑 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><tit…

Git远程控制

文章目录 1. 创建仓库1.1 Readme1.2 Issue1.3 Pull request 2. 远程仓库克隆3. 推送远程仓库4. 拉取远程仓库5. 配置Git.gitignore配置别名 使用GitHub可以&#xff0c;采用Gitee也行 1. 创建仓库 1.1 Readme Readme文件相当于这个仓库的说明书&#xff0c;gitee会初始化2两份…

解除网页禁止选择

控制台输入以下命令 复制&#xff1a;javascript:void(document.body.οncοpy) 可选&#xff1a;javascript:void(document.body.onselectstart) 拖拉&#xff1a;javascript:void(document.body.οnmοuseup)

好的架构是进化来的,不是设计来的

很多年前&#xff0c;读了子柳老师的《淘宝技术这十年》。这本书成为了我的架构启蒙书&#xff0c;书中的一句话像种子一样深埋在我的脑海里&#xff1a;“好的架构是进化来的&#xff0c;不是设计来的”。 2015 年&#xff0c;我加入神州专车订单研发团队&#xff0c;亲历了专…

Android ART 虚拟机简析

源码基于&#xff1a;Android U 1. prop 名称选项名称heap 变量名称功能 dalvik.vm.heapstartsize MemoryInitialSize initial_heap_size_ 虚拟机在启动时&#xff0c;向系统申请的起始内存 dalvik.vm.heapgrowthlimit HeapGrowthLimit growth_limit_ 应用可使用的 max…

先进电气技术 —— 控制理论中的“观测器”概述

一、背景 观测器在现代控制理论中的地位十分重要&#xff0c;它是实现系统状态估计的关键工具。观测器的发展历程可以从以下几个方面概述&#xff1a; 1. 起源与发展背景&#xff1a; 观测器的概念源于对系统状态信息的需求&#xff0c;特别是在只能获取部分或间接输出信息…

如何在.NET中集成SignalR

SignalR 简介 SignalR是一个开放源代码库&#xff0c;可用于简化向应用添加实时Web功能&#xff0c;实时Web功能使服务器端代码能够将内容推送到客户端。 SignalR开源库&#xff1a;https://github.com/SignalR/SignalR SignalR 应用场景 需要高频次从服务器获取信息的应用&am…

java-spring 14 项目启动过程

Spring的启动流程可以归纳为三个步骤&#xff1a; 1、初始化Spring容器&#xff0c;注册内置的BeanPostProcessor的BeanDefinition到容器中 2、将配置类的BeanDefinition注册到容器中 3、调用refresh()方法刷新容器 // 初始化容器 public AnnotationConfigApplicationContex…

弘君资本股市技巧:限售股解禁对市场有何影响?

限售股解禁意味着本来不能在商场上自由生意的股票能够进入二级商场流通了&#xff0c;限售股解禁往往会引起投资者们的高度关注。关于限售股解禁对商场有何影响&#xff0c;弘君资本下面就为大家具体介绍一下。 限售股解禁的影响&#xff1a; 1、股价跌落压力增大。当限售股解…

二.常见算法--贪心算法

&#xff08;1&#xff09;单源点最短路径问题 问题描述&#xff1a; 给定一个图&#xff0c;任取其中一个节点为固定的起点&#xff0c;求从起点到任意节点的最短路径距离。 例如&#xff1a; 思路与关键点&#xff1a; 以下代码中涉及到宏INT_MAX,存在于<limits.h>中…

彩色进度条(C语言版本)

.h文件 #include<stdio.h> #include<windows.h>#define NUM 101 #define LOAD_UP 50 #define LOAD_DOWN 60 #define SLEEP_SLOW 300 #define SLEEP_FAST 70 版本1&#xff1a;&#xff08;初始版&#xff09; //v1 #include "progress.h" int main() …

【云原生】Kubernetes基础命令合集

目录 引言 一、命令概述 &#xff08;一&#xff09;命令分类 &#xff08;二&#xff09;基本语法 二、查看基本信息 &#xff08;一&#xff09;环境指令 1.查看版本信息 2.查看资源对象简写 3.添加补全信息 4.查看日志 5.查看集群信息 &#xff08;二&#xff0…

vue打包部署到springboot,通过tomcat运行

tomcat默认端口 8080springboot端口 9132vue 端口 9131 框架 项目是基于SpringBootVue前后端分离的仓库管理系统 后端&#xff1a;SpringBoot MybatisPlus前端&#xff1a;Node.js Vue element-ui数据库&#xff1a;mysql 一. 打包Vue项目 cmd中输入命令 npm run build 后…

【施磊】C++语言基础提高:深入学习C++语言先要练好的内功

课程总目录 文章目录 一、进程的虚拟地址空间内存划分和布局二、函数的调用堆栈详细过程三、程序编译链接原理1. 编译过程2. 链接过程 一、进程的虚拟地址空间内存划分和布局 任何的编程语言 → \to → 产生两种东西&#xff1a;指令和数据 编译链接完成之后会产生一个可执行…

python将程序运行结果存入txt文本

//其实就是运行下面代码&#xff0c;然后下面代码会通过subprocess再去运行script.py&#xff08;我们的程序代码&#xff09;&#xff0c;然后把它写入oput.txt中。 import subprocess with open(oput.txt, w) as f:subprocess.run([python, script.py], stdoutf, stderrsu…