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.rhkb.cn/news/131882.html

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

相关文章

(二十八)大数据实战——Flume数据采集之kafka数据生产与消费集成案例

前言 本节内容我们主要介绍一下flume数据采集和kafka消息中间键的整合。通过flume监听nc端口的数据&#xff0c;将数据发送到kafka消息的first主题中&#xff0c;然后在通过flume消费kafka中的主题消息&#xff0c;将消费到的消息打印到控制台上。集成使用flume作为kafka的生产…

JAVA设计模式8:装饰模式,动态地将责任附加到对象上,扩展对象的功能

作者主页&#xff1a;Designer 小郑 作者简介&#xff1a;3年JAVA全栈开发经验&#xff0c;专注JAVA技术、系统定制、远程指导&#xff0c;致力于企业数字化转型&#xff0c;CSDN博客专家&#xff0c;阿里云社区专家博主&#xff0c;蓝桥云课讲师。 目录 一、什么是装饰模式二、…

春秋云镜 CVE-2015-1427

春秋云镜 CVE-2015-1427 ElasticSearch RCE 靶标介绍 ElasticSearch RCE 启动场景 漏洞利用 因查询时至少要求es中有一条数据&#xff0c;所以发送如下数据包&#xff0c;增加一个数据&#xff1a; POST /website/blog/ HTTP/1.1 Host: eci-2zedttamjkr80i9iubel.cloudeci…

VMware ubuntu空间越用越大

前言 用Ubuntu 1604编译了RK3399的SDK&#xff0c;之后删了一些多余的文件&#xff0c;df - h 已用21G&#xff0c;但window硬盘上还总用了185GB&#xff0c;采用了碎片整理&#xff0c;压缩无法解决 1 启动Ubuntu后, 安装 VMware Tools(T) 、 2 打开ubuntu终端&#xff0c;压…

Revit SDK 介绍:Ribbon 界面

前言 Revit 通过 API 将完整的 Ribbon 做了保留&#xff0c;同时这些菜单按钮也可以和相应的命令绑定。 内容 运行效果如下所示&#xff1a; 菜单特写&#xff1a; Ribbon Sample 整体是 API 暴露出来的一个 RibbonPanel&#xff0c;对应的接口&#xff1a; namespace Au…

FPGA GTH aurora 8b/10b编解码 PCIE 视频传输,提供2套工程源码加QT上位机源码和技术支持

目录 1、前言免责声明 2、我这里已有的 GT 高速接口解决方案3、GTH 全网最细解读GTH 基本结构GTH 发送和接收处理流程GTH 的参考时钟GTH 发送接口GTH 接收接口GTH IP核调用和使用 4、设计思路框架视频源选择silicon9011解码芯片配置及采集动态彩条视频数据组包GTH aurora 8b/10…

Java native 关键字

如你在看 JDK 的源代码的时候&#xff0c;大概率会看到很多方法使用了 native 关键字。 下面是 String 对象 JDK 中的源代码&#xff0c;就带有了一个 native 关键字。 native 是干什么用的 简单来说就是 Java 的 native 方法的实现不是用 Java 实现的&#xff0c;可能在其他…

Flutter 中的单元测试:从工作流基础到复杂场景

对 Flutter 的兴趣空前高涨——而且早就应该出现了。 Google 的开源 SDK 与 Android、iOS、macOS、Web、Windows 和 Linux 兼容。单个 Flutter 代码库支持所有这些。单元测试有助于交付一致且可靠的 Flutter 应用程序&#xff0c;通过在组装之前先发制人地提高代码质量来确保不…

数据结构与算法(二)——前缀、中缀、后缀表达式

一、前缀表达式&#xff08;波兰表达式&#xff09; 1.1、计算机求值 从右至左扫描表达式&#xff0c;遇到数字时&#xff0c;将数字压入堆栈。遇到运算符时&#xff0c;弹出栈顶的两个数&#xff0c;用运算符对它们做相应的计算&#xff08;栈顶元素 和 次顶元素&#xff09…

Navicat导入Excel数据顺序变了

项目场景&#xff1a; Navicat导入Excel数据 问题描述 从Excel表格中导入数据到数据库中。但是&#xff0c;在导入的过程中&#xff0c;我们常会发现数据顺序出现了问题&#xff0c;导致数据错位&#xff0c;给数据的处理带来了极大的麻烦。 原因分析&#xff1a; 这个问题的…

mybatisplus配置拦截器实现保存加密,输出解密,模糊查询

前言&#xff1a;因公司需求需要把某些实体类的某些字段值进行加密保存&#xff0c;在查询时解密明文输出。现记录两种方式。 一、第一种方式&#xff1a; &#xff08;1&#xff09;使用TableField(typeHandler TypeHandler.class)注解自带的字段类型处理器&#xff0c;写一…

电脑死机的时候,CPU到底在做什么?

电脑死机&#xff0c;应该每个接触计算机的小伙伴都经历过吧。 尤其是早些年&#xff0c;电脑配置还没现在这么高的时候&#xff0c;多开几个重量级应用程序&#xff0c;死机就能如约而至&#xff0c;就算你把键盘上的CTRLALTDELETE按烂了&#xff0c;任务管理器也出不来&…

Mybatis-Genertor逆向工程

1、导入mybaties插件 <build><plugins><plugin><groupId>org.mybatis.generator</groupId><artifactId>mybatis-generator-maven-plugin</artifactId><version>1.4.2</version><dependencies><dependency>…

Error: svn: E155004: Run ‘svn cleanup‘ to remove locks

解决办法如下&#xff1a;点击settings 点击清除缓存按钮&#xff0c;然后再使用svn进行提交更新操作&#xff0c;但是可能还会有其它的错误&#xff0c;比如svn: E230001: Server SSL certificate verification failed&#xff0c;解决这个错误请参考我另一篇文章&#xff1a;…

博客系统(升级(Spring))(一)创建数据库,创建实例化对象,统一数据格式,统一报错信息

博客系统&#xff08;一&#xff09; 博客系统一、创建项目二、建立数据库结构链接服务器和数据库和Redis 三、创建实例化对象四、统一数据结构结构 五、统一报错信息 博客系统 博客系统是干什么的&#xff1f; CSDN就是一个典型的博客系统。而我在这里就是通过模拟实现一个博…

Python+Requests+Excel接口测试实战

1、EXCEL文件接口保存方式&#xff0c;如图。 2、然后就是读取EXCEL文件中的数据方法&#xff0c;如下&#xff1a; 1 import xlrd2 3 4 class readExcel(object):5 def __init__(self, path):6 self.path path7 8 property9 def getSheet(self): 10 …

莫比乌斯召回系统介绍

当前召回系统只能召回相关性高的广告&#xff0c;但不能保证该广告变现能力强。莫比乌斯做了如下两点创新&#xff1a; 在召回阶段&#xff0c;引入CPM等业务指标作为召回依据在召回阶段&#xff0c;引入CTR模型&#xff0c;从而召回更多相关性高且变现能力强的广告 参考 百度…

基于Protege的知识建模实战

一.Protege简介、用途和特点 1.Protege简介 Protege是斯坦福大学医学院生物信息研究中心基于Java开发的本体编辑和本体开发工具&#xff0c;也是基于知识的编辑器&#xff0c;属于开放源代码软件。这个软件主要用于语义网中本体的构建&#xff0c;是语义网中本体构建的核心开发…

Elasticsearch:什么是生成式人工智能?

生成式人工智能定义 给学生的解释&#xff08;基本&#xff09;&#xff1a; 生成式人工智能是一种可以创造新的原创内容的技术&#xff0c;例如艺术、音乐、软件代码和写作。 当用户输入提示时&#xff0c;人工智能会根据从互联网上现有示例中学到的知识生成响应&#xff0c;…

linux安装Sentinal1.8.6

前言&#xff1a; 使用docker search sentinel-dashboard命令&#xff0c;发现docker中的镜像版本过低&#xff0c;由于要配合使用1.8.6&#xff0c;所以这里采用java后台运行sentinel1.8.6-jar的方式。 1、官网下载对应版本jar&#xff08;https://github.com/alibaba/Sentin…