当前位置: 首页 > news >正文

多线程篇(其它容器- CopyOnWriteArrayList)(持续更新迭代)

一、CopyOnWriteArrayList(一)

1. 简介

并发包中的并发List只有CopyOnWriteArrayList。

CopyOnWriteArrayList是一个线程安全的ArrayList,对其进行的修改操作都是在底层的一个复制的数

组(快照)上进行的,也就是使用了写时复制策略。

CopyOnWriteArraylist的类图结构如图:

在CopyOnWriteArrayList的类图中,每个CopyOnWriteArrayList对象里面有一个array数组对象用来存放具体元素,ReentrantLock独

占锁对象用来保证同时只有一个线程对array进行修改。

CopyOnWriteArrayList使用写时复制的策略来保证list的一致性,而获取 - 修改 - 写入三步操作并不是原子性的,所以在增删改的过程

中都使用了独占锁,来保证在某个时间只有一个线程能对list数组进行修改。

另外CopyOnWriteArrayList提供了弱一致性的迭代器,从而保证在获取迭代器后,其他线程对list的修改是不可见的,迭代器遍历的数

组是一个快照。

2. 独占锁

独占锁是一种思想: 只能有一个线程获取锁,以独占的方式持有锁。和悲观锁、互斥锁同义。

Java中用到的独占锁: synchronized,ReentrantLock。

3. 弱一致性的迭代器

遍历列表元素可以使用迭代器。在讲解什么是迭代器的弱一致性前,先举一个例子来说明如何使用迭代器:

    public static void main(String[] args) {CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();list.add("Hello");list.add("World");Iterator<String> iterator = list.iterator();while (iterator.hasNext()) {System.out.println(iterator.next());}}

运行结果:

迭代器的hasNext方法用于判断列表中是否还有元素,next方法则具体返回元素。

好了,下面来看CopyOnWriteArrayList中迭代器的弱一致性是怎么回事,

所谓弱一致性是指返回迭代器后,其他线程对list的增删改对迭代器是不可见的,下面看看这是如何做到的。

在如上代码中,当调用iterator()方法获取迭代器时实际上会返回一个COWIterator对象,COWIterator对象的snapshot变量保存了当

前list的内容,cursor是遍历list时数据的下标。

为什么说snapshot是list的快照呢?

明明是指针传递的引用啊,而不是副本。如果在该线程使用返回的迭代器遍历元素的过程中,其他线程没有对list进行增删改,那么

snapshot本身就是list的array,因为它们是引用关系。但是如果在遍历期间其他线程对该list进行了增删改,那么snapshot就是快照了,

因为增删改后list里面的数组被新数组替换了,这时候老数组被snapshot引用。这也说明获取迭代器后,使用该迭代器元素时,其他线

程对该list进行的增删改不可见,因为它们操作的是两个不同的数组,这就是弱一致性。

示例:演示多线程下迭代器的弱一致性的效果。

public class Atomic {private static final CopyOnWriteArrayList<String> arrayList = newCopyOnWriteArrayList<>();public static void main(String[] args) throws InterruptedException {arrayList.add("hello");arrayList.add("alibaba");arrayList.add("welcome");arrayList.add("to");arrayList.add("hangzhou");Thread threadOne = new Thread(() -> {//修改list中下标为1的元素为babaarrayList.set(1, "baba");//删除元素arrayList.remove(2);arrayList.remove(3);});//保证在修改线程启动前获取迭代器Iterator<String> itr = arrayList.iterator();threadOne.start();// 保证threadOne的run方法执行完毕(完成对arrayList的修改)Thread.sleep(1000);while (itr.hasNext()) System.out.println(itr.next());}
}

运行结果:

在如上代码中,main函数首先初始化了arrayList,然后在启动线程前获取到了arrayList迭代器。

子线程threadOne启动后首先修改了arrayList的第一个元素的值,然后删除了arrayList中下标为2和3的元素。

主线程在子线程执行完毕后使用获取的迭代器遍历数组元素,从输出结果我们知道,在子线程里面进行的操作一个都没有生效,这就是迭

代器弱一致性的体现。

需要注意的是,获取迭代器的操作必须在子线程操作之前进行。

二、CopyOnWriteArrayList(二)

1. 简介

在 ArrayList 的类注释上,JDK 就提醒了我们,如果要把 ArrayList 作为共享变量的话,是线程不安全的,推荐我们自己加锁或者使用

Collections.synchronizedList 方法,其实 JDK 还提供了另外一种线程安全的 List,叫做 CopyOnWriteArrayList

2. 原理

很多时候,我们的系统应对的都是读多写少的并发场景。CopyOnWriteArrayList容器允许并发读,读操作是无锁的,性能较高。至于写

操作,比如向容器中添加一个元素,则首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器。

  • 线程安全的,多线程环境下可以直接使用,无需加锁;
  • 通过锁 + 数组拷贝 + volatile 关键字保证了线程安全;
  • 每次数组操作,都会把数组拷贝一份出来,在新数组上进行操作,操作成功之后再赋值回去。

从整体架构上来说,CopyOnWriteArrayList 数据结构和 ArrayList 是一致的,底层是个数组,只不过 CopyOnWriteArrayList 在对数组进

行操作的时候,基本会分四步走:

  • 加锁;
  • 从原数组中拷贝出新数组;
  • 在新数组上进行操作,并把新数组赋值给数组容器;
  • 解锁

除了加锁之外,CopyOnWriteArrayList 的底层数组还被 volatile 关键字修饰,意思是一旦数组被修改,其它线程立马能够感知到,

代码如下:

private transient volatile Object[] array;

整体上来说,CopyOnWriteArrayList 就是利用锁 + 数组拷贝 + volatile 关键字保证了 List 的线程安全。

3. 优点

读操作(不加锁)性能很高,因为无需任何同步措施,比较适用于读多写少的并发场景。Java的list在遍历时,若中途有别的线程对list容

器进行修改,则会抛ConcurrentModificationException异常。而CopyOnWriteArrayList由于其"读写分离"的思想,遍历和修改操作分别

作用在不同的list容器,所以在使用迭代器进行遍历时候,也就不会抛出ConcurrentModificationException异常了。

4. 缺点

一是内存占用问题,毕竟每次执行写操作都要将原容器拷贝一份。数据量大时,对内存压力较大,可能会引起频繁GC;

二是无法保证实时性,因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新

写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所

以有两份对象内存)。

5. 源码分析

添加操作

    public boolean add(E e) {//ReentrantLock加锁,保证线程安全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();}}

添加的逻辑很简单,先将原容器copy一份,然后在新副本上执行写操作,之后再切换引用。当然此过程是要加锁的。

删除操作

    public E remove(int index) {//加锁final ReentrantLock lock = this.lock;lock.lock();try {Object[] elements = getArray();int len = elements.length;E oldValue = get(elements, index);int numMoved = len - index - 1;if (numMoved == 0)//如果要删除的是列表末端数据,拷贝前len-1个数据到新副本上,再切换引用setArray(Arrays.copyOf(elements, len - 1));else {//否则,将除要删除元素之外的其他元素拷贝到新副本中,并切换引用Object[] newElements = new Object[len - 1];System.arraycopy(elements, 0, newElements, 0, index);System.arraycopy(elements, index + 1, newElements, index,numMoved);setArray(newElements);}return oldValue;} finally {//解锁lock.unlock();}}

删除操作同理,将除要删除元素之外的其他元素拷贝到新副本中,然后切换引用,将原容器引用指向新副本。同属写操作,需要加锁。

我们再来看看读操作,CopyOnWriteArrayList的读操作是不用加锁的,性能很高。

    public E get(int index) {return get(getArray(), index);}

直接读取即可,无需加锁

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

弱一致性的迭代器

所谓弱一致性是指返回迭代器后,其他线程对list的增删改查对迭代器是不可见的

// 演示多线程下迭代器的弱一致性结果
public class copylist {private static volatile CopyOnWriteArrayList<String> arrayList = new CopyOnWriteArrayList<>();public static void main(String[] args) throws InterruptedException {arrayList.add("hello");arrayList.add("alibaba");arrayList.add("welcome");arrayList.add("to");arrayList.add("hangzhou");Thread threadOne = new Thread(new Runnable() {@Overridepublic void run() {// 修改list中下标为1的元素为aliarrayList.set(1, "ali");// 删除元素arrayList.remove(2);arrayList.remove(3);}});// 保证在修改线程启动前获取迭代器Iterator<String> itr = arrayList.iterator();// 启动线程threadOne.start();// 等待子线程执行完毕threadOne.join();while(itr.hasNext()) {System.out.println(itr.next());}}
}

执行程序:

hello
alibaba
welcome
to
hangzhouProcess finished with exit code 0

执行程序:

hello
alibaba
welcome
to
hangzhouProcess finished with exit code 0

从输出结果我们知道,在子线程里面进行的操作一个都没有生效,这就是迭代器弱一致性的体现。

需要注意的是,获取迭代器的操作必须在子线程操作之前进行。

6. ArrayList转为线程安全的方法

List list = Collections.synchronizedList(new ArrayList());

相关文章:

多线程篇(其它容器- CopyOnWriteArrayList)(持续更新迭代)

一、CopyOnWriteArrayList&#xff08;一&#xff09; 1. 简介 并发包中的并发List只有CopyOnWriteArrayList。 CopyOnWriteArrayList是一个线程安全的ArrayList&#xff0c;对其进行的修改操作都是在底层的一个复制的数 组&#xff08;快照&#xff09;上进行的&#xff0…...

OPENAIGC开发者大赛高校组金奖 | 知洞—基于大模型的智慧题库

在第二届拯救者杯OPENAIGC开发者大赛中&#xff0c;涌现出一批技术突出、创意卓越的作品。为了让这些优秀项目被更多人看到&#xff0c;我们特意开设了优秀作品报道专栏&#xff0c;旨在展示其独特之处和开发者的精彩故事。 无论您是技术专家还是爱好者&#xff0c;希望能带给您…...

java服务CPU使用率高排查

第一步 使用top命令进行查看 如果是死锁&#xff0c;cpu使用率不会很高&#xff0c;但不会响应&#xff0c;这时这样排查。 第二步 使用jps查看到进程id,再使用jstack pid查看线程堆栈信息 jstack pid会出现如图所示的信息&#xff0c;表示发生死锁&#xff0c;然后去排查这…...

聚焦:clicOH 借助 NVIDIA cuOpt 实现最后一英里交付速度 20 倍提升

受消费者行为转变和疫情影响&#xff0c;电子商务继续呈爆炸式增长和转型。因此&#xff0c;物流和运输公司发现自己处于包裹配送革命的前沿。这新的现实情况在最后一英里配送中尤为明显&#xff0c;而后者现在已经成为供应链物流中成本最高的要素&#xff0c;占从零售到制造等…...

从头开始嵌入式第三十八天(数据结构 双向链表)

目录 双向链表 一、结构特点 二、操作优势 三、应用场景 1.创建链表 2.头插数据 3.打印数据 4.查找数据 5.删除数据 6.更改数据 7.清空数据 8.尾插数据 9.按位插入 10.获取长度 11.是否为空 双向链表 双向链表是一种链表结构。 一、结构特点 1. 每个节点包含两个…...

chapter14-集合——(List-HashSet)——day18

目录 519-HashSet全面说明 520-数组链表模拟 521-HashSet扩容机制 重要 522-HashSet源码解读1 526-HashSet最佳实践 527-hashSet思考题 519-HashSet全面说明 题一、 两个tom都可以添加成功是因为这是两个对象 看源码做分析&#xff1a;不是直接指向常量池的吗&#xff1f;…...

企业会议室预约管理系统

基于springbootvuemysql实现的企业会议室预约管理系统&#xff08;源码数据库部署视频&#xff09; ### 主要技术 SpringBoot、Vue、MySQL ### 系统角色 员工、管理员 ### 系统功能 1&#xff09;管理员&#xff1a;数据统计&#xff08;会议室使用统计-柱状图、设备状态统计…...

安全API

提到安全&#xff0c;大部分在学习和调试编程语言时并不太在意&#xff0c;真正爆发问题是在用户端。真正的安全漏洞是很可怕的&#xff0c;获取系统最高权限&#xff0c;获得敏感资讯&#xff0c;伤心不已&#xff0c;泪流满面。 字符串 C语言简洁自由的风格&#xff0c;产生…...

【论文阅读】视觉分割新SOTA: Segment Anything(SAM)

导言 随着基于对比文本—图像对的预训练&#xff08;CLIP&#xff09;方法或者模型、聊天生成预训练转换器&#xff08;ChatGPT&#xff09;、生成预训练转换器-4&#xff08;GPT-4&#xff09;等基础大模型的出现&#xff0c;通用人工智能&#xff08; AGI&#xff09;的研究…...

redis之list核心命令演示与细节探索

redis之list核心命令演示与细节探索 BLMOVE source destination LEFT|RIGHT LEFT|RIGHT timeout summary: Pop an element from a list, push it to another list and return it; or block until one is available since: 6.2.0 BLPOP key [key …] timeout summary: Remove …...

[数据集][目标检测]智慧农业草莓叶子病虫害检测数据集VOC+YOLO格式4040张9类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;4040 标注数量(xml文件个数)&#xff1a;4040 标注数量(txt文件个数)&#xff1a;4040 标注…...

Lua 与 C#交互

Lua 与C#交互 前提 Lua是一种嵌入式脚本语言&#xff0c;Lua的解释器是用C编写的&#xff0c;因此可以方便的与C/C进行相互调用。轻量级 Lua语言的官方版本只包括一个精简的核心和最基本的库&#xff0c;这使得Lua体积小、启动速度快&#xff0c;也适合嵌入在别的程序里。 交…...

【办公类】大组工会学习(文心一言+Python批量)

背景需求&#xff1a; 每学期要写一份工会的大组政治学习读后感&#xff08;9月-1月&#xff0c;共5次&#xff09; 学习内容 9月、10月、11月、12月、1月的学习内容文字稿 在班级里&#xff0c;我擅长电脑工作&#xff0c;所以这种写的工作都包了。 中2班三位老师一共写3篇&…...

项目管理工作流是什么?项目管理工作流管理实战技巧!

项目管理工作流是指在协作过程中通过限制任务状态的流转进行流程控制的一种方式。项目从启动到完成所经历的一系列有序、可控的步骤和流程&#xff0c;它详细描述了项目执行过程中各项任务和活动的顺序、依赖关系、责任人以及完成标准等&#xff0c;是项目成功执行的重要保障。…...

leveldb源码剖析(二)——LSM Tree

LSM Tree LSM Tree&#xff1a;Log-Structured Merge Tree&#xff0c;日志结构合并树。是一种频繁写性能很高的数据结构。 LSM Tree将写入操作与合并操作分离&#xff0c;数据首先写入磁盘中的日志文件&#xff08;WAL&#xff09;&#xff0c;随后写入内存缓存&#xff0c;…...

三十六、Gin注册功能-检查账号是否存在

一、初始化 1、在cms.go中添加数据库连接方法 func connDB(app *CmsApp) {mysqlDB, err : gorm.Open(mysql.Open("root:rootroottcp(localhost:3306)/?charsetutf8mb4&parseTimeTrue&locLocal"))if err ! nil {panic(err)}db, err : mysqlDB.DB()if err !…...

什么是期权对冲?

今天期权懂带你了解什么是期权对冲&#xff1f;期权对冲的选择取决于投资者的市场预期和风险承受能力&#xff0c;通过合理使用期权对冲策略&#xff0c;可以有效减少风险并优化投资组合的表现。 期权对冲是什么&#xff1f; 期权是一种支持双向交易的投资产品&#xff0c;期…...

什么是数据库课程设计?

文章目录 前言一、课程设计目的二、课程设计流程三、设计要点四、示例项目总结 前言 数据库课程设计是一个综合性的实践过程&#xff0c;旨在通过实际项目的设计与实现&#xff0c;加深学生对数据库理论知识的理解和应用能力。 以下是一个关于数据库课程设计的基本框架和要点&…...

走进低代码报表开发(二):高效报表设计新利器

在前面的文章中&#xff0c;我们已经详细介绍了勤研低代码开发平台的报表数据源可视化设计&#xff0c;接下来&#xff0c;让我们一起来继续了解勤研低代码平台的报表设计&#xff0c;在当今数字化快速发展的时代&#xff0c;高效便捷的开发工具对于企业和开发者来说至关重要。…...

校园水电费管理|基于java的校园水电费管理小程序系统 (源码+数据库+文档)

校园水电费管理 目录 基于java的校园水电费管理小程序系统 一、前言 二、系统设计 三、系统功能设计 小程序端 后台功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕…...

如何在看板中体现优先级变化

在看板中有效体现优先级变化的关键措施包括&#xff1a;采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中&#xff0c;设置任务排序规则尤其重要&#xff0c;因为它让看板视觉上直观地体…...

2024年赣州旅游投资集团社会招聘笔试真

2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...

python如何将word的doc另存为docx

将 DOCX 文件另存为 DOCX 格式&#xff08;Python 实现&#xff09; 在 Python 中&#xff0c;你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是&#xff0c;.doc 是旧的 Word 格式&#xff0c;而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

Module Federation 和 Native Federation 的比较

前言 Module Federation 是 Webpack 5 引入的微前端架构方案&#xff0c;允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)

宇树机器人多姿态起立控制强化学习框架论文解析 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架&#xff08;一&#xff09; 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

认识CMake并使用CMake构建自己的第一个项目

1.CMake的作用和优势 跨平台支持&#xff1a;CMake支持多种操作系统和编译器&#xff0c;使用同一份构建配置可以在不同的环境中使用 简化配置&#xff1a;通过CMakeLists.txt文件&#xff0c;用户可以定义项目结构、依赖项、编译选项等&#xff0c;无需手动编写复杂的构建脚本…...

JDK 17 序列化是怎么回事

如何序列化&#xff1f;其实很简单&#xff0c;就是根据每个类型&#xff0c;用工厂类调用。逐个完成。 没什么漂亮的代码&#xff0c;只有有效、稳定的代码。 代码中调用toJson toJson 代码 mapper.writeValueAsString ObjectMapper DefaultSerializerProvider 一堆实…...

C++--string的模拟实现

一,引言 string的模拟实现是只对string对象中给的主要功能经行模拟实现&#xff0c;其目的是加强对string的底层了解&#xff0c;以便于在以后的学习或者工作中更加熟练的使用string。本文中的代码仅供参考并不唯一。 二,默认成员函数 string主要有三个成员变量&#xff0c;…...

【iOS】 Block再学习

iOS Block再学习 文章目录 iOS Block再学习前言Block的三种类型__ NSGlobalBlock____ NSMallocBlock____ NSStackBlock__小结 Block底层分析Block的结构捕获自由变量捕获全局(静态)变量捕获静态变量__block修饰符forwarding指针 Block的copy时机block作为函数返回值将block赋给…...

Git 命令全流程总结

以下是从初始化到版本控制、查看记录、撤回操作的 Git 命令全流程总结&#xff0c;按操作场景分类整理&#xff1a; 一、初始化与基础操作 操作命令初始化仓库git init添加所有文件到暂存区git add .提交到本地仓库git commit -m "提交描述"首次提交需配置身份git c…...