当前位置: 首页 > 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;✌️大厂码农|毕…...

java设计模式 桥接模式

桥接模式&#xff08;Bridge Pattern&#xff09;是一种结构型设计模式&#xff0c;旨在将抽象部分与其实现部分分离&#xff0c;使它们都可以独立地变化。桥接模式通过将继承改为组合&#xff0c;实现了在不修改现有类的情况下&#xff0c;动态地切换和扩展抽象类与其具体实现…...

如何利用大数据技术来识别和预防网络赌博行为?

1.构建赌博账户识别模型&#xff1a;通过大数据分析和机器学习技术&#xff0c;建立智能风险防控体系&#xff0c;对账户进行全生命周期管理&#xff0c;精准打击和切断不法分子的资金链条 。 2.分析资金流动&#xff1a;利用大数据技术监测和分析异常资金流动&#xff0c;识别…...

N-152基于java贪吃蛇游戏5

开发工具eclipse,jdk1.8 文档截图&#xff1a; N-152基于java贪吃蛇游戏5...

从线段中搜寻提取闭合轮廓(三)

1.前言 做底层和数据的调试问题也是个麻烦事&#xff0c;如果没有方便的可视化工具辅助&#xff0c;那将令人感到痛苦&#xff0c;借助可视化的工具可以让我们高效、省心&#xff0c;进而心情舒畅&#xff0c;重要的是可以提高调试效率。 当然可视化工具也分不同层次的…...

最全面的递归算法详解,一篇足矣(高手必备)

在编程中&#xff0c;递归和循环是两种常用的控制结构&#xff0c;各有其独特的优缺点。理解这两者的特点和应用场景&#xff0c;对于编写高效、可读的代码至关重要。 什么是递归&#xff1f; 递归是一种强大的编程技术&#xff0c;允许函数在其定义中调用自身。递归通常涉及…...

数据结构(2)单向链表排序和双向链表操作

一单向链表的插入排序 void insertion_sort_link(link_t* plink) { // 如果链表头为空&#xff0c;直接返回 if(NULL plink->phead) { return; } // 初始化指针&#xff0c;p指向当前已排序部分的最后一个节点 node_t* p plink->phead; // ptemp指向待插入的…...

OpenCV结构分析与形状描述符(14)拟合直线函数fitLine()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 拟合一条直线到2D或3D点集。 fitLine 函数通过最小化 ∑ i ρ ( r i ) \sum_i \rho(r_i) ∑i​ρ(ri​)来拟合一条直线到2D或3D点集&#xff0c…...

Mysql基础练习题 1757.可回收且低脂的产品(力扣)

编写解决方案找出既是低脂又是可回收的产品编号。 题目链接&#xff1a; https://leetcode.cn/problems/recyclable-and-low-fat-products/description/ 建表插入数据&#xff1a; Create table If Not Exists Products (product_id int, low_fats ENUM(Y, N), recyclable …...

Nginx调优,有这篇就够了

目录 1. 工作进程数量 2. Nginx最大打开文件数 3. Nginx事件处理模型 4. 开启高效传输模式 5. 连接超时时间 6. proxy调优 7. fastcgi 调优 8. gzip 调优 9. expires 缓存调优 10. 防盗链 11. 内核参数优化 1. 工作进程数量 #根据cpu个数自动调整工作进程数量 work…...

Java语言程序设计基础篇_编程练习题*18.17 (数组中某个指定字符出现的次数)

题目&#xff1a;*18.17 (数组中某个指定字符出现的次数) 编写一个递归的方法&#xff0c;求出数组中一个指定字符出现的次数。需要定义下面两个方法&#xff0c;第二个方法是一个递归的辅助方法。 public static int count(char[] chars, char ch) public static int count(…...