Java CopyOnWriteArrayList

在 Java 的集合中, List 是一个很高频使用的集合中, 但是平时使用的 ArrayList, LinkedList 都是线程不安全的。 线程可见性不支持, 内部的 fast-fail 机制等都是表明他们不适合高频发的场景使用。如果我们需要一个线程安全的列表集合

  1. 使用古老的集合类 Vector
  2. 通过 Collections.synchronizedList(List<T> list) 得到一个线程安全的列表

虽然他们的确可以处理高并发的场景, 但是性能却不太行 (内部几乎所有的操作都是通过加同步锁实现的), 那么是否有更好的选择吗 ?

如果你现在的业务场景是一种读多写少, 同时支持短时间的数据延迟的情况, 那么可以考虑使用一下 CopyOnWriteArrayList, 它是一个设计简单, 采用了写时复制 (Copy-On-Write) 的机制, 以确保在读取和写入操作之间的最终一致性的 List。

1 Copy-On-Write

Copy-On-Write 的特点概括起来就 2 个

  1. 读写分离
  2. 最终一致性

大体的实现如下:

  1. 在集合的内部统一维护着一个数组(链表) 的数据结构, 存储着用户的数据
  2. 用户读取数据时, 直接就从这个数据结构中获取数据即可
  3. 而在用户对内部的数据进行修改时, 则会对修改办法进行加锁, 串行地处理

3.1 获取到锁的线程, 会直接从内部维护的数据结构直接拷贝一份相同的数据
3.2 对拷贝的数据进行修改
3.3 将修改后的数据设置到集合内部统一维护的数据结构

这个过程中

  1. 变更的操作不会影响到读取的操作
  2. 变更后的数据, 也同样不会被立即读取到这就会造成一定时间的数据不一致
  3. 将变更后的数据, 重新设置到集合内部的统一的数据结构后, 最终都能读取到最新的数据, 也就是最终达到数据一致的效果

2 CopyOnWriteArrayList 的实现原理

经过上面的一大篇铺垫, 可能认为 CopyOnWriteArrayList 是一个很复杂的集合类, 实际就是一个简单的数组引用的修改。

下面通过分析源码的进行深入了解一下

public class CopyOnWriteArrayList<E> {/** 真正存储数据的数组 */private transient volatile Object[] array;}

可以看到 CopyOnWriteArrayList 本质就是一个数组, 只是这个数组被 volatile 修饰了(volatile 只能保证对象的引用变更了, 能被所有线程感知到, 但是对引用里面的内容进行变更, 线程是无法感知到的)。

2.1 读取操作

我们先简单看一下 CopyOnWriteArrayList 的读操作

public class CopyOnWriteArrayList<E> {/*** 获取列表中第 index 位的内容*/public E get(int index) {return elementAt(getArray(), index);}/*** 获取存储数据的数组*/final Object[] getArray() {// 直接返回当前的数组变量 arrayreturn array;}/*** 获取指定数组对应位置的数据 */static <E> E elementAt(Object[] a, int index) {// 通过索引读取到入参的数组的指定位置的数据return (E) a[index];}
}

可以看出来整个 get 方法很简单, 没有任何针对并发场景的处理 (CAS, 加锁等)。
因为这个读的操作不涉及都任何的数据变更, 简单实现获取数据的逻辑即可。

2.2 新增数据

public class CopyOnWriteArrayList<E> {public boolean add(E e) {final ReentrantLock lock = this.lock;// 1. 通过一个可重入锁 lock, 保证写线程在同一时刻只有一个, 变为串行的执行lock.lock();try {// 2. 获取到原本存数据的数组的引用Object[] elements = getArray();// 3. 获取到原本数组的长度int len = elements.length;// 4. 创建新的数组, 并将旧数组的数据复制到新数组中Object[] newElements = Arrays.copyOf(elements, len + 1);// 5. 往新数组最后一位添加新的数据newElements[len] = e;// 截至到这一步, 虽然看起来我们已经对集合里面的数据做了修改,// 但是在这之前所有的线程读操作, 还是读取旧数组的数据// 6. 将旧数组引用指向新的数组, 上面的数组变量通过 volatile 修饰了, 将新的数组设置过去, 其他线程都会感知到这个变量变了// 所以变更后的数据, 这时其他线程可以读取到了setArray(newElements);return true;} finally {// 锁释放lock.unlock();}}/*** 更新集合里面的数组*/final void setArray(Object[] a) {// 直接将入参的数组赋值给当前的 array 属性array = a;}
}

整个 add 方法的逻辑整理如下

  1. 竞争锁, 获取锁成功的线程才能继续执行下面的逻辑
  2. 从旧的数组里面拷贝一个新的数组
  3. 追加新的数据到新数组的最后面
  4. 将新的数组替换集合里面的旧数组
  5. 释放锁

这里面涉及到 2 个并发相关的知识

  1. 可重入锁 ReentrantLock, 让整个写的操作变为串行
  2. 修饰 CopyOnWriteArrayList 数组变量的 array, 在数据替换后, 能构让其他线程感知到, 去读取最新的值

3 总结

3.1 Copy-On-Write vs 读写锁

Copy-On-Write 机制和读写锁都是通过读写分离的思想实现的, 但两者还是有些不同。

  1. Copy-On-Write 对写操作做了锁控制, 确保写操作的数据正常, 而对读取操作不做任何的限制, 确保了读取的性能, 但是带来了一定时间 “脏数据” 的问题
  2. 读写锁: 对整个读写操作都加锁, 在有线程在读取数据, 写线程必须等待, 在写线程变更数据的过程, 读操作也必须等待写操作完成, 通过这种等待, 牺牲了一定的性能, 但是确保了数据的一致

3.2 Copy-On-Write 的缺点

  1. 内存占用问题: 因为 Copy-On-Write 的写时复制机制, 所以在进行写操作的时候, 内存里会同时存在新旧两个对象, 这个会导致的内存差不多两倍的消耗, 如果这些对象占用的内存比较大, 可能会造成内存问题
  2. 数据一致性问题: Copy-On-Write 机制只能保证数据的最终一致性, 不能保证数据的实时一致性

3.3 CopyOnWriteArrayList 适用的场景

下面列举了几个读多写少的业务场景

  1. 系统配置管理:
  2. 系统黑白名单管理
  3. 应用内部缓存

列举的这几个基本都是一些变更不频繁, 但是读取高频的场景, 同时短时间的数据延迟同步影响不大, 所以还是挺适合 CopyOnWriteArrayList

4 参考

并发容器之CopyOnWriteArrayList

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

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

相关文章

C++基础 -12- 拷贝构造(浅拷贝)

系统会自动生成浅拷贝&#xff0c;就相当于直接赋值 #include "iostream"using namespace std;class base { public:base(int a, int b) : a(a), b(b){}int a;int b; }; int main() {base newbase1(10, 20);base newbase2 newbase1;cout << newbase1.a <…

另类解决方案Apache(httpd) 报错You don‘t have permission to access xxx /on this server

在一个Kylix系统&#xff08;应该是Redhat/Centos改版&#xff09;的httpd服务器的文档目录下新增加了一个文件&#xff0c;权限也都设置成了644&#xff0c;结果从浏览器访问时&#xff0c;报错&#xff1a;Apache(httpd) 报错You don’t have permission to access /on this …

数据库的增删查改(CRUD)基础版

CRUD: create增加、retrieve查询、update更新、delete删除 注意一点&#xff1a;MySQL对大小写是不敏感的 目录 新增&#xff08;create&#xff09; 全列插入 指定列插入 多行插入 查询&#xff08;Retrieve&#xff09; 列查询 全列查询 指定列查询 表达式查询 …

【SpringBoot】调用外部接口

文章目录 原始httpClient请求使用RestTemplate方法Get请求Post 请求使用Feign进行消费 原始httpClient请求 /** description get方式获取入参&#xff0c;插入数据并发起流程* author lyx* params documentId* return String*///RequestMapping("/submit/{documentId}&quo…

wsl 命令详解

WSL 简介 WSL全称 Windows Subsystem for Linux &#xff0c;是微软开发的一个运行在Windows上的兼容层&#xff0c;它允许开发人员和用户直接在Windows上运行原生Linux二进制文件&#xff0c;而无需配置或修改系统。 WSL命令是用于管理和操作WSL子系统的工具。 常用WSL命令…

Unity优化——脚本优化策略4

大家好&#xff0c;这里是七七&#xff0c;今天这期是脚本优化的最后一期了。下期的主题是批处理的优势&#xff0c;感兴趣的小伙伴们可以收藏本专题&#xff0c;七七会持续更新。 话不多说&#xff0c;开始今天的内容。 一、最小化反序列化行为 Unity的序列化系统主要用于场…

系统设计概念:生产 Web 应用的架构

在你使用的每个完美应用程序背后&#xff0c;都有一整套的架构、测试、监控和安全措施。今天&#xff0c;让我们来看看一个生产就绪应用程序的非常高层次的架构。 CI/CD 管道 我们的第一个关键领域是持续集成和持续部署——CI/CD 管道。 这确保我们的代码从存储库经过一系列测试…

Java核心知识点整理大全22-笔记

目录 19.1.14. CAP 一致性&#xff08;C&#xff09;&#xff1a; 可用性&#xff08;A&#xff09;&#xff1a; 分区容忍性&#xff08;P&#xff09;&#xff1a; 20. 一致性算法 20.1.1. Paxos Paxos 三种角色&#xff1a;Proposer&#xff0c;Acceptor&#xff0c;L…

Akamai + Linode , 为您提供两全其美的 IaaS 云解决方案

进军全球市场&#xff0c;海外扩张布局 面对超大规模云、“替代云”服务 出海企业难免陷入“两难”境地 前者成熟昂贵&#xff0c;后者稳定难保…… 多云战略趋势下&#xff0c;Akamai 能够为您免除这种烦恼&#xff0c;通过收购“替代云”服务的先行者 Linode&#xff0c;我…

YOLOv8独家原创改进:自研独家创新MSAM注意力,通道注意力升级,魔改CBAM

💡💡💡本文自研创新改进:MSAM(CBAM升级版):通道注意力具备多尺度性能,多分支深度卷积更好的提取多尺度特征,最后高效结合空间注意力 1)作为注意力MSAM使用; 推荐指数:五星 MSCA | 亲测在多个数据集能够实现涨点,对标CBAM。 在道路缺陷检测任务中,原始ma…

VMware通过ISO镜像安装window2016虚拟机

1.点文件->新建虚拟机 2.进入到下边页面 3.根据你的服务器硬件选择硬件兼容性 4.选择2016版本的windows(注&#xff1a;没有该版本的话选择最高版本) 5.根据你的需求选择引导设备( 启动过程&#xff1a; BIOS&#xff1a; 在计算机启动时&#xff0c;BIOS负责进行自检&#…

华天动力-OA8000 MyHttpServlet 文件上传漏洞复现

0x01 产品简介 华天动力OA是一款将先进的管理思想、 管理模式和软件技术、网络技术相结合&#xff0c;为用户提供了低成本、 高效能的协同办公和管理平台。 0x02 漏洞概述 华天动力OA MyHttpServlet 存在任意文件上传漏洞&#xff0c;未经身份认证的攻击者可上传恶意的raq文件…

【C 语言经典100例】C 练习实例15

题目&#xff1a;利用条件运算符的嵌套来完成此题&#xff1a;学习成绩>90分的同学用A表示&#xff0c;60-89分之间的用B表示&#xff0c;60分以下的用C表示。 程序分析&#xff1a;(a>b)?a:b这是条件运算符的基本例子。 #include<stdio.h> int main() {int sco…

Kanna库代码示例

编写一个使用Kanna库的网络爬虫程序。以下是代码的详细解释&#xff1a; swift import Kanna // 创建一个对象 let proxy Proxy(host: ") // 创建一个Kanna对象 let kanna Kanna(proxy: proxy) // 创建一个请求对象 let request Request(url: "") // 使用…

【算法刷题】Day8

文章目录 202. 快乐数解法&#xff1a; 11. 盛最多水的容器解法&#xff1a; 202. 快乐数 原题链接 拿到题&#xff0c;我们先看题干 把一个整数替换为每个位置上的数字平方和&#xff0c;有两种情况&#xff1a; 重复这个过程始终不到 1&#xff08;无限死循环&#xff09;结…

python基于YOLOv7系列模型【yolov7-tiny/yolov7/yolov7x】开发构建钢铁产业产品智能自动化检测识别系统

在前文的项目开发实践中&#xff0c;我们已经以钢铁产业产品缺陷检测数据场景为基准&#xff0c;陆续开发构建了多款目标检测模型&#xff0c;感兴趣的话可以自行阅读即可。 《YOLOv3老矣尚能战否&#xff1f;基于YOLOv3开发构建建钢铁产业产品智能自动化检测识别系统&#xf…

seccomp学习 (3)

文章目录 0x06. 其他B. execveat (nr322)C. sendto recvfrom (nr44, 45)D. sendmsg recvmsg (nr46, 47)E. io_uring系列 (nr425,426,427) 本文继续上一篇文章继续介绍seccomp与系统调用的那些事~~~ 0x06. 其他 B. execveat (nr322) long sys_execveat(int dfd, const char…

专业的事交给专业的公司来做,文件销毁 数据销毁 硬盘销毁

在当今信息化社会&#xff0c;数据和文件已经成为企业和个人生活中不可或缺的一部分。然而&#xff0c;随着数据量的不断增长&#xff0c;如何确保数据的安全性和隐私性成为了一个亟待解决的问题。为了解决这个问题&#xff0c;文件销毁、硬盘销毁、数据销毁和物料销毁等技术应…

firewall-cmd --list-all命令详解

firewall-cmd --list-all 可以用于列出系统中所有防火墙规则的详细信息&#xff0c;包括已激活的防火墙区域、服务和端口等。 这些信息能够帮助您了解系统中当前已配置的防火墙规则&#xff0c;以及允许或禁止访问的服务和端口等相关信息。 [rootlocalhost ~]# firewall-cmd -…

人工智能与我们的生活

人工智能对我们的生活影响有多大 1. 人工智能的领域 人工智能涉及的领域广泛&#xff0c;包括但不限于&#xff1a; a. 医疗保健领域 人工智能在医疗诊断、药物研发、患者管理等方面发挥了重要作用。医疗影像分析、基因组学研究等都受益于人工智能技术&#xff0c;为医学领…