Java 写时复制容器 —— CopyOnWriteArrayList

引言

写时复制的含义是当容器发生修改操作时,如add() 等,就会将原来的容器整体复制一份,这个过程是加锁的。而如果只是读取资源,例如 get() ,就不会受到任何同步要求的限制。

写时复制的理念是,如果多个读取线程请求相同的数据,它们会共享相同的数据,而不需要考虑并发修改的问题不得不在线程内部生成一份数据副本;当容器发生修改操作时,系统这时才会真正复制一个副本给其他请求者,也就是说,写时复制的理念最主要是解决访问者需要考虑并发修改的问题,有了这种机制,就可以避免生成线程副本或加锁访问,只要容器没有被修改,就不会产生任何数据副本。在很大程度上提高了读的性能。

但缺点显而易见,如果容器依然有大量的修改操作,或读写比不高的话,使用写时复制容器只会降低程序性能。

一、CopyOnWriteArrayList

1.1 写入效率

public class T04_CopyOnWriteList {public static void runAndComputeTime(Thread[] ths) {long s1 = System.currentTimeMillis();Arrays.stream(ths).forEach(t -> t.start());Arrays.stream(ths).forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}});long s2 = System.currentTimeMillis();System.out.println(s2 - s1 + " ms");}public static void main(String[] args) {
//        List<String> list = new CopyOnWriteArrayList<>(); // output:7565 ms
//        List<String> list = new Vector<>(); // output:53 msList<String> list = Collections.synchronizedList(new ArrayList<>()); // output:52 msThread[] ths = new Thread[100];for (int i = 0; i < ths.length; i++) {Runnable task = () -> {for (int j = 0; j < 1000; j++) {list.add("a" + j);}};ths[i] = new Thread(task);}runAndComputeTime(ths);System.out.println(list.size());}
}

上述代码模拟了100个线程,每个线程向 list 中加入1000个字符串的操作。

runAndComputeTime() 方法先是启动线程,然后通过 join 方法,依次将所有线程合并到主线程中,这么做的目的主要是让全部线程的执行时间累加,从而得出一个总时间。

最后输出消耗时间和 list 存储数量,消耗时间不必多说, list 存储数量主要是看并发场景下线程安全性,不能出现“丢失数据”的情况,想一想 如果用 ArrayList,最终 size() 方法输出多少?

从输出结果来看,两个同步容器执行效率相当,都是 50 ~ 80 ms 左右,而CopyOnWriteArrayList 执行效率最长,达到了惊人的 7500 ms。

所以,如果不是确定写入操作极少,就一定不要使用 CopyOnWriteArrayList!

1.2 读取效率

public class T05_CopyOnWriteList {private static List<String> list = new CopyOnWriteArrayList<>(); // output:66ms
//    private static List<String> list = new Vector<>();// output:956ms
//    private static List<String> list = Collections.synchronizedList(new ArrayList<>());// output:873msstatic {for (int i = 0; i < 100000; i++) {list.add(String.valueOf(i));}}public static void main(String[] args) {Thread[] ths = new Thread[100];for (int i = 0; i < ths.length; i++) {Runnable task = () -> {for (int j = 0; j < list.size(); j++) {list.get(j);}};ths[i] = new Thread(task);}System.out.println("开始...");long t1 = System.currentTimeMillis();Arrays.stream(ths).forEach(t -> t.start());Arrays.stream(ths).forEach(t -> {try {t.join();} catch (InterruptedException e) {e.printStackTrace();}});long t2 = System.currentTimeMillis();System.out.println(t2 - t1 + "ms");}
}

上述代码中,先通过 static 块初始化了一个 list,然后通过 100个线程并发读取 list 中的数据,最后通过 join 累加所有过程的执行时间,并输出消耗时间。

从执行结果来看,同步容器的执行效率在 800~900ms 左右,而 CopyOnWriteArrayList 可以达到惊人的 百毫秒以内,效率提升了十倍以上。

所以,如果确定一批数据的写入操作极少,而读取操作非常频繁的话,可以考虑使用 CopyOnWriteArrayList 容器,线程安全+读性能可观。

二、CopyOnWriteArrayList 源码分析

写时复制容器的写入操作包括 add 、set 等,实现逻辑几乎完全一致,以 add() 为例:

    public boolean add(E e) {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();}}

容器内部维护了一个 ReentrantLock 作为锁的实现,在执行 add 操作时,先进行锁定。

然后取得底层数组,并拷贝一个长度 +1 的新数组,并将新元素放入最后。

将容器指针指向新的数组后,unlock 解锁。

由此可见,写入慢的原因就不言自明了,加锁、整个数组拷贝,这两个逻辑就是写入慢的真正的元凶。

我们再来看下读取的逻辑,以 get() 为例:

public E get(int index) {return get(getArray(), index);
}@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {return (E) a[index];
}

整个获取元素的过程未加任何锁,原因就是容器已经在修改的时候保证了同步逻辑,极大的提升了读取的效率。

总结

写时复制的观念就是在修改时复制容器的副本,从而避免在读取时需要考虑额外的并发修改问题。

写时复制容器的应用场景是写入操作极少,读取操作非常多的情况,切不可在包含大量写入操作的场景下使用 CopyOnWrite 。

Java 的写时复制容器实现是 CopyOnWriteArrayList,其底层就是一个定长数组,当容器发生修改时,会使用容器内的 ReentrantLock 上锁,并拷贝整个数组完成操作。

当发生读取时,可以像单线程那样不需要加任何同步机制,可以让多线程并发读取的效率达到最大。

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

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

相关文章

arm中断保护和恢复_浅谈ARM处理器的七种异常处理

昨天的文章&#xff0c;我们谈了ARM处理器的七种运行模式&#xff0c;分别是&#xff1a;用户模式User(usr)&#xff0c;系统模式System(sys)&#xff0c;快速中断模式(fiq)&#xff0c;管理模式Supervisor(svc)&#xff0c;外部中断模式(irq)&#xff0c;数据访问中止模式Abor…

Queue —— JUC 的豪华队列组件

目录引言一、Queue 的继承关系1.1 Queue 定义基础操作1.2 AbstractQueue 为子类减负1.3 BlockingQueue 阻塞式Queue1.4 Deque 两头进出二、Queue 的重要实现三、BlockingQueue 的实现原理四、Queue 在生产者消费者模式中的应用五、Queue 在线程池中的应用六、ConcurrentLinkedQ…

daad转换器实验数据_箔芯片电阻在高温应用A/D转换器中的应用

工业/应用领域高温&#xff1a;地震数据采集系统、石油勘探监测、高精度检测仪产品采用&#xff1a;V5X5 Bulk Metal (R) Foil芯片电阻案例介绍TX424是一个完整的4通道24位模数转换器&#xff0c;采用40脚封装。该设计采用最先进设计方案&#xff0c;两个双通道24位调节器和一个…

excel分段排序_学会这个神操作,报表填报不再五花八门,效率远超Excel

在报表工作人员的的日常工作中&#xff0c;常常要面临统计混乱的终端用户输入的问题。由于无法准确限制用户的输入内容&#xff0c;所以在最终进行数据统计时&#xff0c;常常会出现数据不合法的情况。为此需要花费大量的人力和时间核对校验数据。举个简单的例子&#xff0c;某…

IDEA——必备插件指南

目录一、Free-Mybatis-Plugin二、Lombok三、jclasslib Bytecode Viewer一、Free-Mybatis-Plugin 二、Lombok 三、jclasslib Bytecode Viewer 学习 class 文件的必备插件。 使用简单&#xff0c;安装后可以在菜单 View 中看到 show bytecode with jclasslib&#xff1a; 效果…

jitter 如何优化网络_如何做好关键词优化网络?

越来越多的传统企业开始建立自己的网站&#xff0c;进而不断的推广自己的产品。为了能够让自己的企业网站出现在搜索引擎的首页&#xff0c;现在最常用的手段就是竞价排名和关键词优化网络。往往很多企业会选择关键词优化网络这种方式来推广自己的网站&#xff0c;对于新手seoe…

python学生名片系统_Python入门教程完整版400集(懂中文就能学会)快来带走

如何入门Python&#xff1f;权威Python大型400集视频&#xff0c;学了Python可以做什么&#xff1f;小编今天给大家分享一套高老师的python400集视频教程&#xff0c;里面包含入门进阶&#xff0c;源码&#xff0c;实战项目等等&#xff0c;&#xff0c;不管你是正在学习中&…

JVM——详解类加载过程

导航一、过程概述二、Loading2.1 类加载器2.2 双亲委派机制2.3 类在内存中的结构三、Linking四、Initializing一、过程概述 java 源文件编译后会生成一个 .class文件存储在硬盘上。 在程序运行时&#xff0c;会将用到的类文件加载到 JVM 内存中。从磁盘到内存的过程总共分为三…

pkpm板按弹性计算还是塑性_[转载]双向板按弹性还是按塑性方法计算

双向板按弹性方法还是按塑性方法计算茅老师您好&#xff01;想请教您个问题&#xff0c;PKPM计算双向板时一般都是按弹性算吧&#xff0c;可我去年刚进设计院的时候有一个项目是按塑性算的&#xff0c;这样影响大不大啊&#xff0c;支座与跨中弯矩比值系数取得默认的1.8&#x…

Java 的混合执行模式

导航解释执行与编译执行总结解释执行与编译执行 Java 虽然是先编译再运行&#xff0c;但实际上&#xff0c;对于 JVM 来说&#xff0c;依然是逐条解释执行字节码文件中的指令&#xff0c;即大部分情况下&#xff0c;Java 都是解释执行的。 JVM通过 interpreter 解释器解释执行…

下载 Java 学习的权威文档

JVMS 和 JLS 文档的下载 快速直达&#xff1a; https://docs.oracle.com/javase/8/ --> Java Language and Virtual Machine Specifications jvm specification 和 java language specification 是Java 学习的两个最权威的文档。如果你用的是 Java 8&#xff0c;就可以去下载…

iso图像测试卡_4700万像素 五轴防抖 徕卡正式发布SL2无反相机

出自蜂鸟网-器材频道&#xff0c;原文链接&#xff1a;https://m.fengniao.com/document/5358989.html徕卡于今日正式发布SL2相机&#xff0c;搭载4700万像素CMOS感光元件、通过感光元件移位实现光学图像稳定的五轴防抖技术、全新徕卡物距探测式自动对焦技术以及576万像素分辨率…

friendly发音_friendly是什么意思_friendly怎么读_friendly翻译_用法_发音_词组_同反义词_友好的_亲密的-新东方在线英语词典...

词汇搭配用作形容词 (adj.)&#xff5e;名词friendly co-operation友好合作&#xff5e;介词friendly to对…友好,有利于…friendly to a cause支持〔有利于〕某事业friendly towards sb对某人友好friendly with sb与某人亲热词组短语environmentally friendly保护生态环境的&a…

JVM——对象的创建与内存布局

导航一、对象的创建过程二、对象的内存布局2.1 内存布局2.2 计算对象的内存大小三、对象的定位3.1 句柄池3.2 直接指针四、对象的分配过程一、对象的创建过程 对象&#xff0c;又叫实例&#xff0c;是 OOP 的最常用角色。 如何创建一个对象&#xff1f;一般都是使用 new 关键…

不安装cudnn可不可以_关于CUDA和cuDNN的安装

不得不说&#xff0c;安装显卡驱动和CUDA、cuDNN是深度学习工作者的必备技能。CUDA(Compute Unified Device Architecture)&#xff0c;是NVIDIA推出的运算平台。cuDNN 专门针对Deep Learning框架设计的一套GPU计算加速方案。虽然安装这不是学习的目的&#xff0c;但却是很多人…

常用自定义注解

导航一、方法计时器二、valid 参数校验的通用返回三、接口访问频次拦截&#xff08;幂等&#xff09;一、方法计时器 注解类&#xff1a;MethodTimer Target({ElementType.METHOD}) Retention(RetentionPolicy.RUNTIME) public interface MethodTimer { }处理器&#xff08;需…

python 系统架构_Python之优化系统架构的方案

方案3&#xff1a; 改变系统架构在开始多进程之前&#xff0c;先简单说明一下python GIL, 之前自己对他也有些误解。因为python GIL的机制存在&#xff0c;同时运行的线程只有一个&#xff0c;但这个线程在不同时刻可以运行在不同的核上&#xff0c;这个调度是由操作系统完成的…

JVM垃圾收集器——G1

导航引言一、G1 介绍1.1 适用场景1.2 设计初衷1.3 关注焦点1.4 工作模式1.5 堆的逻辑结构1.6 主要收集目标1.7 停顿预测模型1.8 拷贝和压缩1.9 与 CMS 和 Parallel 收集器的比较1.10 固定停顿目标二、堆的逻辑分区2.1 region2.2 CSet2.3 RSet2.4 Card Table三、G1 的工作原理3.…

的mvc_简述PHP网站开发的MVC模式

为了提高开发时候的代码重用和开发速度&#xff0c;php使用了mvc的模式&#xff0c;主要是对代码的功能进行了分类&#xff0c;M&#xff1a;model主要是对数据库进行操作&#xff0c;v&#xff1a;view主要是前端html文件操作&#xff0c;c&#xff1a;controller主要是编写基…

CAP 原则与 BASE 理论

导航引言一、CAP 原则1.1 Consistency 一致性1.2 Available 可用性1.3 Partition tolerance 分区容错性1.4 CAP 的矛盾1.5 CAP 的组合场景二、BASE 理论2.1 基本可用2.2 软状态2.3 最终一致性2.3.1 因果一致性2.3.2 读自身所写2.3.3 会话一致性2.3.4 单调读一致性2.3.5 单调写一…