深入理解CopyOnWriteArrayList源码分析

上篇推荐:Java中快速失败 (fail-fast) 机制

CopyOnWriteArrayList简介

CopyOnWriteArrayList是java.util.concurrent包下提供的一个线程安全的ArrayList。它通过一个简单的策略来保证线程安全:当我们需要修改列表时(增加、删除、修改等操作),而不是直接对当前的内容进行操作,它会将当前的内容复制一份,在副本上执行修改然后将原列表指向新的副本

源码分析与加锁机制

CopyOnWriteArrayList使用了ReentrantLock来实现线程安全的修改操作。下面是一个简化版的源码展示其加锁机制:

private transient volatile Object[] array;
final transient ReentrantLock lock = new ReentrantLock();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中效率最高的操作,因为它可以直接访问底层数组,无需加锁:

public E get(int index) {return get(getArray(), index);
}private E get(Object[] a, int index) {return (E) a[index];
}

修改

修改操作和增加、删除操作类似,首先是数组的复制,然后在新数组上修改指定位置的元素值,之后再将原数组指向新数组。

适用场景

CopyOnWriteArrayList适合读多写少的并发场景。由于它的读取操作不需要加锁,可以充分利用硬件和操作系统的优化,如缓存一致性协议,使得读取操作非常高效。而写操作,由于涉及到复制整个数组,所以在数据量大、写操作频繁的场景中性能会相对较低。

问题

  • 内存消耗:由于写操作需要复制整个数组,对于大规模数据的列表,这可能导致相当昂贵的内存消耗。
  • 数据一致性:CopyOnWriteArrayList保证最终一致性而非实时一致性。在写操作发生的同时,读取操作可能仍然读到旧的数据。
  • 迭代器弱一致性:迭代器不会反映出在迭代器创建之后的修改。

迭代器弱一致性 举例说明

public static void main(String[] args) {CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();list.add("a");list.add("b");list.add("c");Iterator<String> iterator = list.iterator();// 在迭代进行中对列表进行修改list.add("d");list.remove("b");while (iterator.hasNext()) {System.out.println(iterator.next());}}
a
b
c

在迭代的过程中,迭代器只能看到最开始的快照,可能会错过后来添加或删除的元素。因此,虽然不会抛出异常,但迭代的结果可能是一个旧的快照,从而出现了迭代的弱一致性。

总的来说,CopyOnWriteArrayList是一种读写分离的思路,在特定的适用场景下可以提供很好的并发性能和线程安全性。然而,开发者在使用时需要注意它的限制和适用的局限性,以确保应用程序的性能和正确性。

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

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

相关文章

102 cesium 切换底图为黑色

1.切换cesium底图为黑色 // 底图const baseLayer viewer.imageryLayers.get(0);if (baseLayer.show) {baseLayer.show false;viewer.scene.globe.baseColor Cesium.Color.BLACK;} else {baseLayer.show true;} 2.地下模式 async toggleUnderground(item: any) {item.activ…

电子学会C/C++编程等级考试2023年03月(四级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:最佳路径 如下所示的由正整数数字构成的三角形: 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,和最大的路径称为最佳路径。你的任务就是求出最…

Linux之重谈文件和c语言文件接口

重谈文件 文件 内容 属性, 所有对文件的操作都是: a.对内容操作 b.对属性操作 关于文件 一&#xff1a; 即使文件的内容为空&#xff0c;该文件也会在磁盘上也会占空间&#xff0c;因为文件不仅仅只有内容还有文件对应的属性&#xff0c;文件的内容会占用空间, 文件的属性也…

gitlab 迁移-安装-还原

文章目录 一、备份原有Gitlab1、备份清单2、备份执行 二、卸载删除原有Gitlab1、停止Gitlab2、卸载Gitlab3、查看Gitlab进程4、杀死进程5、删除所有包含Gitlab文件 三、安装Gitlab1、添加镜像地址2、安装依赖3、安装防火墙4、下载安装Gitlab5、配置Gitlab6、启动并访问 四、还原…

Linux基本指令(2.0)

周边知识&#xff1a; 1.Linux中&#xff0c; 一切皆文件 构建大文件 输入如下shell命令 i1; while [ $i -le 10000]; do echo "hello Linux $i"; let i; done 此时大文件已经创建在big.txt 此时我们发现cat查看无法查看开始内容 我们使用more 当占满一屏之后就不…

Unity-Shader - 2DSprite描边效果

实现一个简单的2D精灵图描边效果&#xff0c;效果如下 实现思路&#xff1a; 可以通过判断该像素周围是否有透明度为 0的值&#xff0c;如果有&#xff0c;则说明该像素位于边缘。 所以我们需要打开alpha blend&#xff0c;即&#xff1a; Blend SrcAlpha OneMinusSrcAlpha&am…

单实例应用程序

2023年12月6日&#xff0c;周三凌晨 什么是单实例应用程序 单实例应用程序可以确保在同一时间只有一个应用程序实例在运行。 通常情况下&#xff0c;当用户尝试再次启动一个已经启动过的应用程序时&#xff0c;操作系统会打开一个新的实例。但有些情况下&#xff0c;我们可能…

js中的栈(stack)和堆(heap)

什么是堆什么是栈&#xff1f; 程序运行时候&#xff0c;需要内存空间存放数据。系统划分出的两种内存空间就叫做stack&#xff08;栈&#xff09;和heap&#xff08;堆&#xff09;。 栈&#xff08;stack&#xff09;&#xff1a;由操作系统自动分配内存空间&#xff0c;自…

透明度值和注意点

透明度 透明度分为256阶&#xff08;0-255&#xff09;&#xff0c;计算机上用16进制表示为&#xff08;00-ff&#xff09;。透明就是0阶&#xff0c;不透明就是255阶,如果50%透明就是127阶&#xff08;256的一半当然是128&#xff0c;但因为是从0开始&#xff0c;所以实际上是…

react项目中使用video标签设置自动播放并未及时播放解决

react项目中使用video标签设置autoplay,但是视频不会直接播放&#xff0c;会加载一段时间后才会自动播放。 解决&#xff1a; 手动调用play方法 const videoRef useRef();useEffect(() > { if(videoRef?.current){if(videoRef?.current.paused){videoRef?.current.pla…

leetcode:1422. 分割字符串的最大得分(python3解法)

难度&#xff1a;简单 给你一个由若干 0 和 1 组成的字符串 s &#xff0c;请你计算并返回将该字符串分割成两个 非空 子字符串&#xff08;即 左 子字符串和 右 子字符串&#xff09;所能获得的最大得分。 「分割字符串的得分」为 左 子字符串中 0 的数量加上 右 子字符串中 1…

Android 12.0 Folder文件夹全屏后文件夹图标列表居中时拖拽app到桌面的优化

1.概述 在12.0的系统rom产品开发中,在Launcher3中在目前的产品需求开发中,对于Launcher3中的文件夹Folder的布局UI 进行了定制化的需求要求把Folder修改为全屏,然后在中间显示文件夹图标的列表,这时候如果Folder是全屏的话,如果拖拽文件夹列表中的app图标,只有拖拽 到屏…

html复习

html form表单作用是收集数据提交 input框体控件不在form不适用于表单提交 可编辑性:contenteditable 提示值消失&#xff1a;placeholder&#xff0c;value是初始数据 块标签&#xff1a;单独占有一个空间&#xff0c;独占一行&#xff0c;标签遵循从上到下排列。table、d…

UEC++ 探索虚幻5笔记(捡金币案例) day12

吃金币案例 创建金币逻辑 之前的MyActor_One.cpp&#xff0c;直接添加几个资源拿着就用 //静态网格UPROPERTY(VisibleAnywhere, BlueprintReadOnly)class UStaticMeshComponent* StaticMesh;//球形碰撞体UPROPERTY(VisibleAnywhere, BlueprintReadWrite)class USphereCompone…

【Linux知识点汇总】04 Linux软件包管理器RPM常用命令

RPM&#xff08;Red Hat Package Manager&#xff09;是一种用于在基于Red Hat的Linux发行版中安装、卸载、更新和管理软件包的工具 查看和显示命令 说明命令查看已安装的rpm包rpm -qa查询某个rpm包rpm -q pkg_name查看已安装rpm包提供的配置⽂件rpm -qc pkg_name查看⼀个包安…

【水】pytorch:torch.reshape和torch.Tensor.view的区别

【水】pytorch&#xff1a;torch.reshape和torch.Tensor.view的区别 注&#xff1a;本篇仅为学习笔记&#xff0c;请谨慎参考&#xff0c;如有错误请评论指出。 参考&#xff1a;Pytorch: view()和reshape()的区别&#xff1f;他们与continues()的关系是什么&#xff1f; 两者…

Flink流批一体计算(23):Flink SQL之多流kafka写入多个mysql sink

目录 1. 准备工作 生成数据 创建数据表 2. 创建数据表 创建数据源表 创建数据目标表 3. 计算 WITH子句 1. 准备工作 生成数据 source kafka json 数据格式 &#xff1a; topic case_kafka_mysql&#xff1a; {"ts": "20201011","id"…

JSON 语法详解:轻松掌握数据结构(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

postgresql-effective_cache_size参数详解

在 PostgreSQL 中&#xff0c;effective_cache_size 是一个配置参数&#xff0c;用于告诉查询规划器关于系统中可用缓存的估计信息。这个参数并不表示实际的内存量&#xff0c;而是用于告诉 PostgreSQL 查询规划器系统中可用的磁盘缓存和操作系统级别的文件系统缓存的大小。它用…

Lambda表达式用法汇总

Lambda表达式用法汇总 java8 中引入的 Lambda 表达式真的是个好东西&#xff0c;掌握之后&#xff0c;写代码更简洁了&#xff0c;码字效率也提升了不少&#xff0c;这里咱 们一起来看看 Lambada 表达式常见的写法&#xff0c;加深理解。 1、有参无返回值函数式接口 8 种写法…