CopyOnWriteArrayList介绍

二、CopyOnWriteArrayList

2.1 CopyOnWriteArrayList介绍

CopyOnWriteArrayList是一个线程安全的ArrayList。

CopyOnWriteArrayList是基于lock锁和数组副本的形式去保证线程安全。

在写数据时,需要先获取lock锁,需要复制一个副本数组,将数据插入到副本数组中,将副本数组赋值给CopyOnWriteArrayList中的array。

因为CopyOnWriteArrayList每次写数据都要构建一个副本,如果你的业务是写多,并且数组中的数据量比较大,尽量避免去使用CopyOnWriteArrayList,因为这里会构建大量的数组副本,比较占用内存资源。

CopyOnWriteArrayList是弱一致性的,写操作先执行,但是副本还有落到CopyOnWriteArrayList的array属性中,此时读操作是无法查询到的。

2.2 核心属性&方法

主要查看2个核心属性,以及2个核心方法,还有无参构造

/** 写操作时,需要先获取到的锁资源,CopyOnWriteArrayList全局唯一的。 */
final transient ReentrantLock lock = new ReentrantLock();/** CopyOnWriteArrayList真实存放数据的位置,查询也是查询当前array */
private transient volatile Object[] array;// 获取array属性
final Object[] getArray() {return array;
}// 替换array属性
final void setArray(Object[] a) {array = a;
}/***  默认new的CopyOnWriteArrayList数组长度为0。*  不像ArrayList,初始长度是10,每次扩容1/2, CopyOnWriteArrayList不存在这个概念*  每次写的时候都会构建一个新的数组*/
public CopyOnWriteArrayList() {setArray(new Object[0]);
}

2.3 读操作

CopyOnWriteArrayList的读操作就是get方法,基于数组索引位置获取数据。

方法之所以要差分成两个,是因为CopyOnWriteArrayList中在获取数据时,不单单只有一个array的数组需要获取值,还有副本中数据的值。

// 查询数据时,只能通过get方法查询CopyOnWriteArrayList中的数据
public E get(int index) {// getArray拿到array数组,调用get方法的重载return get(getArray(), index);
}
// 执行get(int)时,内部调用的方法
private E get(Object[] a, int index) {// 直接拿到数组上指定索引位置的值return (E) a[index];
}

2.4 写操作

CopyOnWriteArrayList是基于lock锁和副本数组的形式保证线程安全。

// 写入元素,不指定索引位置,直接放到最后的位置
public boolean add(E e) {// 获取全局锁,并执行lockfinal ReentrantLock lock = this.lock;lock.lock();try {// 获取原数组,还获取了原数组的长度Object[] elements = getArray();int len = elements.length;// 基于原数组复制一份副本数组,并且长度比原来多了一个Object[] newElements = Arrays.copyOf(elements, len + 1);// 将添加的数据放到副本数组最后一个位置newElements[len] = e;// 将副本数组,赋值给CopyOnWriteArrayList的原数组setArray(newElements);// 添加成功,返回truereturn true;} finally {// 释放锁~lock.unlock();}
}// 写入元素,指定索引位置。(不会覆盖数据)
public void add(int index, E element) {// 拿锁,加锁~final ReentrantLock lock = this.lock;lock.lock();try {// 获取原数组,还获取了原数组的长度Object[] elements = getArray();int len = elements.length;// 如果索引位置大于原数组的长度,或者索引位置是小于0的。if (index > len || index < 0)throw new IndexOutOfBoundsException("Index: "+index+", Size: "+len);// 声明了副本数组Object[] newElements;// 原数组长度 - 索引位置等到numMovedint numMoved = len - index;// 如果numMoved为0,说明数据要放到最后面的位置if (numMoved == 0)// 直接走了原生态的方式,正常复制一份副本数组newElements = Arrays.copyOf(elements, len + 1);else {// 数组要插入的位置不是最后一个位置// 副本数组长度依然是原长度 + 1newElements = new Object[len + 1];// 将原数组从0索引位置开始复制,复制到副本数组中的前置位置System.arraycopy(elements, 0, newElements, 0, index);// 将原数组从index位置开始复制,复制到副本数组的index + 1往后放。// 这时,index就空缺出来了。System.arraycopy(elements, index, newElements, index + 1,numMoved);}// 数据正常放到指定的索引位置newElements[index] = element;// 将副本数组,赋值给CopyOnWriteArrayList的原数组setArray(newElements);} finally {// 释放锁lock.unlock();}
}

2.5 移除数据

关于remove操作,要分析两个方法

  • 基于索引位置移除指定数据

  • 基于具体元素删除数组中最靠前的数据

    • 当前这种方式,嵌套了一层,导致如果元素存在话,成本是比较高的。
    • 如果元素不存在,这种设计不需要加锁,提升写的效率
// 删除指定索引位置的数据
public E remove(int index) {// 拿锁,加锁final ReentrantLock lock = this.lock;lock.lock();try {// 获取原数组和原数组长度Object[] elements = getArray();int len = elements.length;// 通过get方法拿到index位置的数据E oldValue = get(elements, index);// 声明numMovedint numMoved = len - index - 1;// 如果numMoved为0,说明删除的元素是最后的位置if (numMoved == 0)// Arrays.copyOf复制一份新的副本数组,并且将最后一个数据不要了// 基于setArray将副本数组赋值给array原数组setArray(Arrays.copyOf(elements, len - 1));else {// 删除的元素不在最后面的位置// 声明副本数组,长度是原数组长度 - 1Object[] newElements = new Object[len - 1];// 从0开始复制的index前面System.arraycopy(elements, 0, newElements, 0, index);// 从index后面复制到最后System.arraycopy(elements, index + 1, newElements, index,numMoved);setArray(newElements);}// 返回被干掉的数据return oldValue;} finally {// 释放锁lock.unlock();}
}// 删除元素(最前面的)
public boolean remove(Object o) {// 没加锁!!!!// 获取原数组Object[] snapshot = getArray();// 用indexOf获取元素在数组的哪个索引位置// 没找到的话,返回-1int index = indexOf(o, snapshot, 0, snapshot.length);// 如果index < 0,说明元素没找到,直接返回false,告辞// 如果找到了元素的位置,直接执行remove方法的重载return (index < 0) ? false : remove(o, snapshot, index);
}
// 执行remove(Object o),找到元素位置时,执行当前方法
private boolean remove(Object o, Object[] snapshot, int index) {// 拿锁,加锁final ReentrantLock lock = this.lock;lock.lock();try {// 拿到原数组和长度Object[] current = getArray();int len = current.length;// findIndex: 是给if起标识,break 标识的时候,直接跳出if的代码块~~if (snapshot != current) findIndex: {// 如果没进到if,说明数组没变化,按照原来的index位置删除即可// 进到这,说明数组有变化,之前的索引位置不一定对// 拿到index位置和原数组长度的值int prefix = Math.min(index, len);// 循环判断,数组变更后,是否影响到了要删除元素的位置for (int i = 0; i < prefix; i++) {// 如果不相等,说明元素变化了。// 同时判断变化的元素是否是我要删除的元素oif (current[i] != snapshot[i] && eq(o, current[i])) {// 如果满足条件,说明当前位置就是我要删除的元素index = i;break findIndex;}}// 如果for循环结束,没有退出if,说明元素可能变化了,总之没找到要删除的元素// 如果删除元素的位置,已经大于等于数组长度了。if (index >= len)// 超过索引范围了,没法删除了。return false;// 索引还在范围内,判断是否是还是原位置,如果是,直接跳出if代码块if (current[index] == o)break findIndex;// 重新找元素在数组中的位置index = indexOf(o, current, index, len);// 找到直接跳出if代码块// 没找到。执行下面的return falseif (index < 0)return false;}// 删除套路,构建新数组,复制index前的,复制index后的Object[] newElements = new Object[len - 1];System.arraycopy(current, 0, newElements, 0, index);System.arraycopy(current, index + 1,newElements, index,len - index - 1);// 复制到arraysetArray(newElements);// 返回true,删除成功return true;} finally {lock.unlock();}
}

2.6 覆盖数据&清空集合

覆盖数据就是set方法,可以将指定位置的数据替换

// 覆盖数据
public E set(int index, E element) {// 拿锁,加锁final ReentrantLock lock = this.lock;lock.lock();try {// 获取原数组Object[] elements = getArray();// 获取原数组的原位置数据E oldValue = get(elements, index);// 原数据和新数据不一样if (oldValue != element) {// 拿到原数据的长度,复制一份副本。int len = elements.length;Object[] newElements = Arrays.copyOf(elements, len);// 将element替换掉副本数组中的数据newElements[index] = element;// 写回原数组setArray(newElements);} else {// 原数据和新数据一样,啥不干,把拿出来的数组再写回去setArray(elements);}// 返回原值return oldValue;} finally {// 释放锁lock.unlock();}
}

清空就是清空了~~~

public void clear() {// 加锁final ReentrantLock lock = this.lock;lock.lock();try {// 扔一个长度为0的数组setArray(new Object[0]);} finally {lock.unlock();}
}

2.7 迭代器

用ArrayList时,如果想在遍历的过程中去移除或者修改元素,必须使用迭代器才可以。

但是CopyOnWriteArrayList这哥们即便用了迭代器也不让做写操作

不让在迭代时做写操作是因为不希望迭代操作时,会影响到写操作,还有,不希望迭代时,还需要加锁。

// 获取遍历CopyOnWriteArrayList的iterator。
public Iterator<E> iterator() {// 其实就是new了一个COWIterator对象,并且获取了array,指定从0开始遍历return new COWIterator<E>(getArray(), 0);
}static final class COWIterator<E> implements ListIterator<E> {/** 遍历的快照 */private final Object[] snapshot;/** 游标,索引~~~ */private int cursor;// 有参构造private COWIterator(Object[] elements, int initialCursor) {cursor = initialCursor;snapshot = elements;}// 有没有下一个元素,基于遍历的索引位置和数组长度查看public boolean hasNext() {return cursor < snapshot.length;}// 有没有上一个元素public boolean hasPrevious() {return cursor > 0;}// 获取下一个值,游标动一下public E next() {// 确保下个位置有数据if (! hasNext())throw new NoSuchElementException();return (E) snapshot[cursor++];}// 获取上一个值,游标往上移动public E previous() {if (! hasPrevious())throw new NoSuchElementException();return (E) snapshot[--cursor];}// 拿到下一个值的索引,返回游标public int nextIndex() {return cursor;}// 拿到上一个值的索引,返回游标public int previousIndex() {return cursor-1;}// 写操作全面禁止!!public void remove() {throw new UnsupportedOperationException();}public void set(E e) {throw new UnsupportedOperationException();}public void add(E e) {throw new UnsupportedOperationException();}// 兼容函数式编程@Overridepublic void forEachRemaining(Consumer<? super E> action) {Objects.requireNonNull(action);Object[] elements = snapshot;final int size = elements.length;for (int i = cursor; i < size; i++) {@SuppressWarnings("unchecked") E e = (E) elements[i];action.accept(e);}cursor = size;}
}

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

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

相关文章

HTML111111111

在线编辑器 在线 HTML 空元素 没有内容的 HTML 元素被称为空元素。空元素是在开始标签中关闭的。 即使 在所有浏览器中都是有效的&#xff0c;但使用 其实是更长远的保障。 HTML 水平线 标签在 HTML 页面中创建水平线。 hr 元素可用于分隔内容。 HTML 折行 如果您希望…

CentOS上安装Mellanox OFED

打开Mellanox官网下载驱动 Linux InfiniBand Drivers 点击下载链接跳转至 Tgz解压缩执行 ./mlnxofedinstall发现缺少模块 # ./mlnxofedinstall Logs dir: /tmp/MLNX_OFED_LINUX.11337.logs General log file: /tmp/MLNX_OFED_LINUX.11337.logs/general.log Verifying KMP rpm…

IPv6测试指标包括哪些方面?如何去测试IPv6?(国科云)

我国高度重视IPv6的发展建设&#xff0c;将IPv6视作未来互联网发展的方向之一&#xff0c;从2017年《推进互联网协议第六版&#xff08;IPv6&#xff09;规模部署行动计划》印发以来&#xff0c;又多次发布IPv6相关的政策法规&#xff0c;以推进IPv6的规模化部署。在政策引导和…

【分布式技术】Elastic Stack部署,实操logstash的过滤模块常用四大插件

目录 一、Elastic Stack&#xff0c;之前被称为ELK Stack 完成ELK与Filebeat对接 步骤一&#xff1a;安装nginx做测试 步骤二&#xff1a;完成filebeat二进制部署 步骤三&#xff1a;准备logstash的测试文件filebeat.conf 步骤四&#xff1a;完成实验测试 二、logstash拥有…

时序分解 | Matlab实现CEEMDAN+PE自适应噪声完备集合经验模态分解+排列熵计算

时序分解 | Matlab实现CEEMDANPE自适应噪声完备集合经验模态分解排列熵计算 目录 时序分解 | Matlab实现CEEMDANPE自适应噪声完备集合经验模态分解排列熵计算效果一览基本介绍程序设计参考资料 效果一览 基本介绍 CEEMDANPE自适应噪声完备集合经验模态分解排列熵计算 运行环境m…

云轴科技ZStack 助力广西某地级市建设市级警务云视频系统

某市属于广西壮族自治区辖地级市&#xff0c;省域副中心城市&#xff0c;选择云轴科技ZStack 超融合解决方案支撑警务云视频监控联网管理系统&#xff08;警务云视频系统&#xff09;&#xff0c;实现了该市对各辖区视频资源统一管理&#xff1b;同时也满足了该市警务云视频系统…

Git教程学习:01 Git简介与安装

目录 1 版本控制1.1 什么是版本控制系统&#xff1f;1.2 本地版本控制系统1.3 集中式版本控制系统1.4 分布式版本控制系统 2 Git简史3 Git的安装3.1 在Linux上安装3.2 初次运行Git前的配置 1 版本控制 1.1 什么是版本控制系统&#xff1f; 版本控制系统(Version Control Syst…

2.【C语言】(函数指针||sizeof||笔试题)

0x01.函数指针 void test(const char* str) {printf("%s\n", str); }int main() {void (*pf)(const char*) test;//pf是函数指针变量void (*pfarr[10])(const char*);//pfarr是存放函数指针的数组void (*(*p)[10])(const char*) &pfarr;//p是指向函数指针数组…

C++编译:明明设置了链接库,但链接时找不到符号 undefined reference to ....

原因可能是库编译是的g环境与当前环境不同&#xff0c;比如 库编译时的glibc版本为2.17而当前的glibc版本为2.31 查看glibc版本&#xff1a;ldd --version

时空预测 | 基于图神经网络的时空预测

引言 时间序列是用于记录动态系统测量的主要数据类型&#xff0c;由物理传感器和在线过程&#xff08;虚拟传感器&#xff09;大量生成。因此&#xff0c;时间序列分析对于解锁可用数据中隐含的信息财富至关重要。随着图神经网络&#xff08;GNNs&#xff09;的最新进展&#x…

PHP 调用 e 签宝接口签名指南

前言 在 401 问题上卡了 一段时间&#xff0c;参考官网文档和鉴权签名计算测试也试了很久&#xff0c;签名确定是没错的&#xff0c;但是一直提示 INVALID_SIGNATURE 其实问题在于我忽略了 公共请求头格式 中 Content-MD5 部分的一句话&#xff1a; GET 和 DELETE 请求且 Bod…

根据基因名批量查找它的Uniprot编号

背景&#xff1a; 前几天老师交给我一个任务&#xff0c;给我一个基因列表&#xff0c;让我查找它们所编码的蛋白质的蛋白质序列。我上了一下uniprot数据库&#xff0c;发现这个任务可以分成两步&#xff1a; 找到这个基因在Uniprot数据库中所对应的蛋白质编码根据蛋白质编码…

聚铭入选“2023中国数字安全能力图谱(精选版)”安全运营领域

近日&#xff0c;国内权威数字安全领域第三方调研机构数世咨询正式发布《2023年度中国数字安全能力图谱&#xff08;精选版&#xff09;》。聚铭网络作为国内领先的安全运营商&#xff0c;凭借在细分领域突出优势&#xff0c;成功入选该图谱“安全运营”领域代表厂商。 据悉&a…

【Linux系统编程】程序地址空间

进程地址空间 进程地址空间是指每个进程在计算机内存中所占用的地址空间。地址空间是指能被访问的内存地址范围&#xff0c;它由若干个连续的内存块组成。每个进程都有自己的地址空间&#xff0c;这意味着每个进程都有自己的内存地址范围&#xff0c;不会与其他进程冲突。进程地…

品牌如何做好有效推广?真诚是关键

品牌推广是一项长期的任务&#xff0c;为了让推广内容达到理想效果&#xff0c;媒介盒子认为需要掌握一定的技巧&#xff0c;今天媒介盒子就从几个方面和大家聊聊品牌如何做好有效推广。 一、 深入了解消费者的需求和偏好 品牌只有真正了解消费者的需求&#xff0c;才有可能提…

C++_Lambda表达式的完整介绍

目录 1. 什么是Lambda表达式 1.1 四种表达式的含义 1.2 lambda表达式各个成员的解释 2. 捕获列表 3. 编译器如何看待Lambda表达式 参考文章 参考: C Lambda表达式的完整介绍 - 知乎 c在c11标准中引入了lambda表达式&#xff0c;一般用于定义匿名函数&#xff0c;使得代码…

云HIS为连锁医院机构提供统一医院管理解决方案

云HIS重建统一的信息架构体系&#xff0c;重构管理服务流程&#xff0c;重造病人服务环境&#xff0c;向不同类型的医疗机构提供SaaS化HIS服务解决方案。 云HIS优势 1、云端数据优势 在传统的HIS模式里&#xff0c;数据存于医院本身的服务器机组&#xff0c;一旦发生故障&…

【QA】Linux-CentOS彻底卸载MySQL

文章概述 Linux系统下&#xff0c;若要卸载干净默认的MySQL&#xff0c;从而安装新的版本&#xff0c;需要卸载干净&#xff0c;不然会在不经意间影响后续安装 文章演示的系统&#xff1a;Linux CentOS 卸载步骤 1、查询是否安装了MySQL [rootlocalhost ~]# rpm -qa | gre…

unity打包apk后网络请求提示unknown error处理

近期同事的一个比较老的版本的unity项目在电脑上运行都正常&#xff0c;但是打包成android后安装到手机上就提示unknown error 让我帮他排查一下问题。由于是涉密项目不能发图就简单介绍下处理过程吧&#xff01; 一、故障原因 请求的地址ssl证书过期了。 二、处理过程 更改请…

Redis--Geo指令的语法和使用场景举例(附近的人功能)

文章目录 前言Geo介绍Geo指令使用使用场景&#xff1a;附近的人参考文献 前言 Redis除了常见的五种数据类型之外&#xff0c;其实还有一些少见的数据结构&#xff0c;如Geo&#xff0c;HyperLogLog等。虽然它们少见&#xff0c;但是作用却不容小觑。本文将介绍Geo指令的语法和…