写时复制集合 —— CopyOnWriteArrayList

Java集合框架:并发集合专题之CopyOnWriteArrayList的写时复制机制与适用场景(18) CopyOnWriteArrayList是Java并发包中一颗独特的明珠,它通过写时复制这种以空间换时间的策略,在特定场景下提供了卓越的读取性能。就像我们生活中某些专业工具一样,它不是万能的,但在适合的场合下表现惊艳。工作原理:写操作复制整个底层数组,读操作无锁最佳场景:读多写少、数据规模不大的并发环境主要优势:无锁读取、线程安全、迭代安全使用代价:写操作昂贵、内存占用较高替代方案:根据场景考虑同步List、ConcurrentHashMap等。 阅读详情

前言

 

JUC 下面还有一个系列的类,都是 CopyOnWriteXXX ,意思是写时复制,这个究竟是怎么回事?那就以 CopyOnWriteArrayList 为切入点,一起了解写时复制是怎么回事?

介绍

ArrayList 的一个线程安全的变体,其中所有可变操作(add、set 等等)都是通过对底层数组进行一次新的复制来实现的。

像名字一样,每次进行操作的时候,都会进行一次复制,当然会有很大的性能消耗,但是在某些使用场景下,又会提高性能。具体是怎么操作的,那就一步一步阅读源码,然后再做总结归纳。

基本使用

public class CopyOnWriteArrayListTest {

    public static void main(String[] args) {

        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();

        // 添加元素
        list.add("liuzhihang");

        // 移除元素
        list.remove("liuzhihang");

        // 查看元素
        String value0 = list.get(0);

        // 遍历
        Iterator<String> iterator = list.iterator();
        while (iterator.hasNext()) {
            String next = iterator.next();
        }

    }
}
复制代码

问题疑问

  1. 为什么要叫写时复制集合?
  2. CopyOnWriteArrayList 实现原理是什么?
  3. CopyOnWriteArrayList 和 ArrayList 有什么区别?
  4. CopyOnWriteArrayList 复制是怎么进行复制的?

源码分析

基本结构

PRT4mY-DDwfkF

参数介绍

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8673264195747942595L;

    /** 数据有变动时使用 */
    final transient ReentrantLock lock = new ReentrantLock();

    /** 数组 只能通过 getArray/setArray 访问 */
    private transient volatile Object[] array;

    final Object[] getArray() {
        return array;
    }
    // 将数组指向传入的新数组
    final void setArray(Object[] a) {
        array = a;
    }
}
复制代码

通过参数可以了解到以下内容:

  1. 基于数组实现;
  2. 使用了 ReentrantLock 互斥锁。

构造函数

public CopyOnWriteArrayList() {
    setArray(new Object[0]);
}
复制代码

在初始化 CopyOnWriteArrayList 时,就是创建了一个 Object 的数组。

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();
    }
}
复制代码

add 方法逻辑很简单:

  1. 通过加互斥锁(ReentrantLock)从而保证在写的时候只有一个线程可以写。
  2. 新增元素时,先使用 Arrays.copyOf(elements, len + 1) 复制出一个长度 +1 的新数组。
  3. 添加元素到新数组。
  4. 然后再将原数组对象指向新数组。

画图如下:

KCTNMP-kLOtFP

remove

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)
            // 如果移除最后一个元素
            // 直接复制前面的元素即可
            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();
    }
}
复制代码

remove 方法相对多了一些判断:

  1. 通过加互斥锁(ReentrantLock)从而保证在写的时候只有一个线程可以移除元素。
  2. 如果移除的是最后一个元素,则直接复制前面的元素到新数组,并指向新数组即可。
  3. 如果移除的是中间的元素,则需要进行两次复制,然后指向新数组。

画图如下:

q3iE0c-AGgXfP

get

public E get(int index) {
    return get(getArray(), index);
}
复制代码

get 方法可以看出:

  1. 获取元素并没有进行加锁。
  2. 从原数组获取的元素。

所以并发情况下,并不能保证很及时的读取的刚插入或者移除的元素。

数组复制

通过阅读 add 和 remove 相关代码,可以看到在数组复制时使用了 Arrays.copyOf 和 System.arraycopy,这相当于一个优化方面吧。

毕竟数组复制总不能把原数组遍历一遍,挨着赋值到新数组里面吧。

那接下来看一下内部是如何实现的:

public class Arrays {
    
    // 其他方法省略 ...
    
    /**
     * original 要复制的数组
     * newLength 新数组的长度
     */
    public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }

    /**
     * original 要复制的数组
     * newLength 新数组的长度
     */
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        // 创建一个新数组,长度是指定的长度
        @SuppressWarnings("unchecked")
        T[] copy = ((Object)newType == (Object)Object[].class)
            ? (T[]) new Object[newLength]
            // 创建具有指定的组件类型和长度的新数组
            : (T[]) Array.newInstance(newType.getComponentType(), newLength);
        
        // 调用 System.arraycopy 复制数组
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }
}
复制代码

通过阅读 Arrays.copyOf 相关源码,发现其实 Arrays.copyOf 底层也是调用的 System.arraycopy

public final class System {

    // 其他方法省略 ...

    /**
     * src 源数组
     * srcPos 源数组起始位置
     * dest 目标数组
     * destPos 目标数组的起始位置
     * length 要复制的元素数量
     */
    public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);
}
复制代码

可以看到 System.arraycopy 是一个 native 方法,这个是 JVM 内部实现的,具体可以阅读相关资料。而使用这种方式要比 for 循环和 clone 要高效很多。

总结

Q&A

Q: 为什么要叫写时复制集合?

A: 因为在 add、remove 操作时会复制出来一个新数组。

Q: CopyOnWriteArrayList 实现原理是什么?

A: 在 add、remove 操作时会进行加锁,然后复制出来一个新数组,操作的都是新数组,而此时原数组是可以提供查询的。当操作结束之后,会将对象指针指向新数组。

Q: CopyOnWriteArrayList 和 ArrayList 有什么区别?

A: CopyOnWriteArrayList 在读多写少的场景下可以提高效率,而 ArrayList 只是普通数组集合,并不适用于并发场景,而如果对 ArrayList 加锁,则会影响一部分性能。

同样对 CopyOnWriteArrayList 而言,仅能保证最终一致性。因为刚写入的数据,是写到的复制的数组中,此时并不能立即查询到。如果要保证实时性可以尝试使用 Collections.synchronizedList 或者加锁等方式。

Q: CopyOnWriteArrayList 复制是怎么进行复制的?

A: 内部使用的是本地方法 System.arraycopy 进行数组的复制。

结束语

通过阅读 CopyOnWriteArrayList 源码,了解到写时复制是的原理。同时了解到可以使用 System.arraycopy 的方式提高数组复制的效率。

同样 CopyOnWriteArrayList 适合读多写少的场景,满足最终一致性,但是并不能保证数据修改及时查询到。

 

魔兽RPG老地图逆向工程:解密三国列传3.0武将招募限制(附JASS实战) 本文详细解析了《三国列传3.0》魔兽RPG地图的武将招募限制机制,通过逆向工程和JASS脚本修改技术,突破SLK加密防线并解密游戏逻辑。文章提供了从工具准备到代码修改的完整实战指南,帮助玩家解除武将数量限制,重温经典游戏的乐趣。 阅读详情

相关推荐

物联网技术(AIR780E cat1-4G模块+小程序+腾讯云MQTT)驱动的智能井盖系统设计(连载01)

终端设备采用AIR780E模块作为主控器,电子模块有三轴加速度传感器、可燃气体传感器,AIR780E模块(4G模块)通过MQTT通信协议进行数据打包上传至EMQX服务器,在EMQX服务器中,用户可以自行订阅主题和发布主题,小程序通过MQTT通信协议订阅EMQX服务器的主题,服务器通过接收到硬件发布的数据,转发到微信小程序。

leida_3669的博客 2218

CopyOnWriterArrayList 详解

在 ArrayList 的类注释上,JDK 就提醒了我们,如果要把 ArrayList 作为共享变量的话,是线程不安全的,推荐我们自己加锁或者使用 Collections.synchronizedList 方法,其实 JDK 还提供了另外一种线程安全的 List,叫做 CopyOnWriteArrayList。......

GeGe&YoYo的博客 1万+

PAT|中国大学MOOC-浙江大学翁恺-C语言程序设计习题集(更新中)

##2-0. 整数四则运算 时间限制:400 ms 内存限制:65536 kB 代码长度限制:8000 B 判题程序:Standard 作者:乔林(清华大学) 本题要求编写程序,计算2个正整数的和、差、积、商并输出。题目保证输入和输出全部在整型范围内。 输入格式: 输入在一行中给出2个正整数A和B。 输出格式: 在4行中按照格式“A 运算符 B = 结果”顺序输出和、差、积、商。 输入样例: 3 ...

小鱼波波波 6万+

CopyOnWriteArrayList详解

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_33979363的博客 404

Supervisor 安装与使用

一、Supervisor 介绍 Supervisor 是一个用 Python 编写的进程管理工具,它可以用于监控和控制类 UNIX 操作系统上的多个进程。它是一个客户端/服务器系统,其中 Supervisor 的服务器端称为 supervisord,负责启动管理的子进程、响应客户端命令、重启崩溃或退出的子进程、记录子进程的 stdout 和 stderr 输出,以及处理子进程生命周期中的事件。客...

疯子丶pony的博客 3863

从牛顿冷却定律到热传导方程:一维热传导方程的物理直觉与数学推导

本文从牛顿冷却定律出发,结合傅里叶热传导定律和能量守恒原理,系统推导了一维热传导方程的数学形式。通过微元分析和物理直觉,揭示了温度随时间变化与空间二阶导数之间的关系,并阐释了热扩散率α的物理意义。文章还探讨了方程的边界条件、数值解法及其在材料科学和工程中的应用,为理解热量传递提供了清晰的物理图像和数学框架。

palm99的专栏 837

CopyOnWriteArrayList原理

文章目录一、 CopyOnWriteArrayList介绍二、 CopyOnWriteArrayList原理三、 CopyOnWriteArrayList 属性介绍四、 构造器以及添加add方法五、 get(int index)六、 remove(int index)七、 遍历 本文基于jdk-11.0.9.101-hotspot 一、 CopyOnWriteArrayList介绍 它相当于线程安全的ArrayList。和ArrayList一样,它是个可变数组;但是和ArrayList不同的时,它具

大佛拈花的博客 3万+

矢量网络分析仪校准:原理、方法与系统适配性

矢量网络分析仪(VNA)校准是射频测量的关键环节,通过误差建模(12项误差项)和标准件测量,可将系统不确定度从±5dB降至±0.1dB以下。主流校准方法包括SOLT(适用于同轴系统)和TRL(适用于波导系统),二者在标准件、误差重点及频率上限等方面存在显著差异。随着6G与量子通信的发展,动态实时校准将成为新趋势。工程师需根据传输结构、频率范围及精度需求选择合适方法,确保测量精度。

u014344410的博客 262

【并发编程】CopyOnWriteArrayList详解与原理

本文讲解什么是CopyOnWriteArrayList以及他的特点、适用场景、缺点,CopyOnWriteArrayList的数据结构、add()、get() 方法、remove()方法的实现原理。

小明的Java问道之路 5071

生成自定义工具栏

我们生成了和VBA时代几乎一样的常用的工具栏。用WPF也可以完成以后的插件开发,界面控件编写基本靠自己绘画,还有用代码去控制,代码结构像HTML,做过网站的应该可以很快入手。其它后台C#代码除了对控件代码的控制外,和WinForm基本相同,但目前来看用WPF是生成自定义工具栏的较好的办法。当然,你也可以在工具栏命令中调用WinForm程序,对Coreldraw进行操作,这也应该是比较高效的办法,下次会讨论。资源的加入是本次调试的难点之一,我们的目标是让图片资源嵌入到程序中去,不必去电脑文件目录去调用;

力先生C#程序开发 1374

PointNet++改进策略目录

后续我将如何使用文章中创新点加入的PointNet++中代码实现部分进行更新。

u012901740的博客 2196

写时复制集合 —— CopyOnWriteArrayList 源码原理阅读笔记

前言 JUC 下面还有一个系列的类,都是 CopyOnWriteXXX ,意思是写时复制,这个究竟是怎么回事?那就以 CopyOnWriteArrayList 为切入点,一起了解写时复制是怎么回事? 公众号:liuzhihangs,记录工作学习中的技术、开发及源码笔记;时不时分享一些生活中的见闻感悟。欢迎大佬来指导! 介绍 ArrayList 的一个线程安全的变体,其中所有可变操作(add、set 等等)都是通过对底层数组进行一次新的复制来实现的。 像名字一样,每次进行操作的时候,都会进行一次复制,.

程序员小航 300

ros_gz_bridge__镜像__readme

Run。

1113

arrays中copyof复制两个数组_写时复制集合 —— CopyOnWriteArrayList

前言JUC 下面还有一个系列的类,都是 CopyOnWriteXXX ,意思是写时复制,这个究竟是怎么回事?那就以 CopyOnWriteArrayList 为切入点,一起了解写时复制是怎么回事?公众号:liuzhihangs,记录工作学习中的技术、开发及源码笔记;时不时分享一些生活中的见闻感悟。欢迎大佬来指导!介绍ArrayList 的一个线程安全的变体,其中所有可变操作(add、set 等等)...

weixin_39792803的博客 227

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

Java集合类源码剖析:CopyOnWriteArrayList的写时复制策略详解

是Java集合框架中的一个线程安全的ArrayList实现,它允许多个线程同时读取,而写入操作则通过创建底层数组的新副本来实现线程安全。通过巧妙的写时复制策略,在读多写少的并发场景下提供了卓越的性能表现。其设计思想体现了计算机科学中典型的空间换时间优化策略。核心要点回顾1. 写操作通过复制整个数组保证线程安全2. 读操作无需同步,性能极高3. 迭代器具有弱一致性特性4. 适合读多写少的并发场景在实际使用中,开发者需要根据具体的应用场景特点来权衡选择。对于高并发的读场景,

2501_93833961的博客 296

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

集合的线程安全性——CopyOnWriteArrayList写时复制

常用的集合ArrayList,LinkedList,HashSet,TreeSet,HashMap,TreeMap等均为线程不安全集合。当运行以下程序时,会报java.util.ConcurrentModificationException异常 public class ContinerNotSafeDemo { public static void main(String[] arg...

叶先森很生气 988

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

JUC集合 CopyOnWriteArrayList 写时复制数组,源码学习

概述 CopyOnWriteArrayList是ArrayList 的一个线程安全的变体,其中所有可变操作(add、set 等等)都是通过对底层数组进行一次新的拷贝来实现的。COW模式的体现。 成员变量 lock :用于修改性操作时需要加的重入锁。 array:是底层存放元素的数组,在多线程操作下,其引用经常变化。 /** 保护所有数据更改操作的锁 */ final transient ReentrantLock lock = new ReentrantLock(); /** 数组。

qq_30921153的博客 297

基于写时复制技术的并发集合———CopyOnWriteArrayList源码分析

CopyOnWriteArrayList作为一种线程安全的集合,它通过加Lock锁来实现线程安全,并且它的写是复制技术是指在对集合进行添加或修改。删除等操作时,不会直接对当前集合进行操作,而是根据原集合进行复制一份出来,然后再对拷贝的集合进行操作,当对新集合写入完毕时,再将原集合的引用指向当前的新集合,从而实现写时复制。CopyOnWriteArrayList内部使用Object[]类型数组存储数据,它允许多个线程并发读取,但在写入时,只能有一个线程操作当前集合。

卡多希y的博客 625

9 ,集合类不安全,并发修改异常,写时复制,CopyOnWriteArrayList

1 ,new ArrayList() 意义 : 构造空的 List 初始长度 : 10 不够用了,扩容 : 原值的一半,长度成为 15 2 ,线程不安全的例子 :并发修改异常 并发修改异常 : ConcurrentModificationException 3 ,解决方法 1 : Vector 线程安全 ( 不推荐 ) 几乎不用他 他可以解决线程安全问题 但是 : 他是以牺牲并发为条件...

孙砚秋的博客 409

Java并发44:并发集合系列-基于写时复制的CopyOnWriteArrayList和CopyOnWriteArraySet

[超级链接:Java并发学习系列-绪论] [系列序章:Java并发43:并发集合系列-序章] 原文地址:http://ifeve.com/java-copy-on-write/ Copy-On-Write简称COW,是一种用于程序设计中的优化策略。 COW的基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这...

韩超的博客 (hanchao5272) 1449

Android ---【经验篇】CopyOnWriteArrayList 的使用场景及讲解

读操作不加锁:由于每次写操作都会创建一个新的数组,读操作可以在不加锁的情况下进行,因此对于读多写少的应用场景,CopyOnWriteArrayList 比其他线程安全集合(如 Vector 或 synchronizedList)效率更高。这意味着,所有的写操作都是不可变的,不会影响到正在执行的读操作,从而避免了线程竞争的问题。适用于读多写少的场景:由于写操作会复制数组,这会带来一定的性能开销,因此它非常适合读多写少的场景。需要线程安全的列表:在多线程环境下,使用它可以确保线程安全而不需要显式的同步。

梁同学与Android 807

CopyOnWriteArrayList详细解释

是一种非常适合读操作频繁、写操作较少的场景下使用的线程安全集合类。它通过写时复制的机制,实现了无锁的并发读取,确保了线程安全的同时避免了读操作的锁竞争。因此,在高并发读的场景下,`CopyOnWriteArrayList` 具有很高的性能优势。然而,的写操作代价较高,因为每次写入都需要复制整个数组,并且还会增加内存消耗。因此,它并不适合频繁修改或大规模写操作的场景。如果应用场景中需要频繁的读写操作,可以考虑其他同步机制,如或关键字的结合使用。

Flying_Fish_roe的博客 910

Java 入门指南:Java 并发编程 —— Copy-On-Write 写时复制技术

CopyOnWrite 是 Java 中一种常用的并发编程技术,指的是在修改共享资源时,不直接修改原始数据,而是在新的副本上进行操作,并最终将修改结果写回原始数据。它的核心思想是:可以容忍读操作并发,但写操作需要互斥执行(写时复制),牺牲了数据的实时性。这种技术通过减少数据共享时的并发冲突,提高了系统的整体效率和稳定性。

热带鱼的技术博客 2358

面试高频考点!CopyOnWriteArrayList 到底是什么?

先来个场景,某次面试,我原本信心满满,BAT大厂二面官,眉头一扬直接来了句:“那你说说 CopyOnWriteArrayList 吧。CopyOnWriteArrayList 这个家伙吧,本质上就像写日记:写的时候影印一份,谁爱怎么改怎么改,看的时候一律享受历史快照,不用担心中间被捣乱。结果 CopyOnWriteArrayList 压根不甩你,遍历期间的写操作,压根不报错,也不会“瞬时生效”,遍历用的是。,活脱脱“井水不犯河水”,你删除你自己的,遍历我还是老样子,完全看不到你的骚操作。

2501_92441786的博客 316

集合之线程安全--java中有哪些线程安全的集合

集合类型线程安全原理适用场景写时复制 + 锁读多写少的列表写时复制 + 锁读多写少的集合分段锁(JDK 7)或 CAS + synchronized高并发的键值对存储跳表 + CAS需要有序的键值对存储跳表 + CAS需要有序的集合锁 + 条件变量生产者-消费者模型方法级别加锁(synchronized)需要线程安全的集合Vector方法级别加锁(synchronized)不推荐使用Hashtable方法级别加锁(synchronized)不推荐使用。

zx733988的博客 1242
上一篇: 刺激的十月,字节三面鞭尸,最终被滴滴捞起,看我如何一步一步攻克面试官?
下一篇: 阿里P8大佬最新整理153道Spring全家桶面试题(含答案)
java架构师小芷
博客等级 码龄6年 216粉丝 519原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值