【JUC并发编程】18 CopyOnWriteArrayList源码也就够看2分钟

news2024/10/1 21:32:16

文章目录

  • 1、CopyOnWriteArrayList概述
  • 2、原理 / 源码
    • 1)构造函数
    • 2、add()
    • 3)get()
    • 4)remove()
    • 5)iterator()

1、CopyOnWriteArrayList概述

CopyOnWriteArrayList相当于线程安全的ArrayList,底层是一个可变数组。
在这里插入图片描述

特点如下:

  1. 线程安全:基于ReentrantLock实现同步;
  2. add/set/remove操作均是基于数组的copy进行;
  3. 迭代器进行遍历的速度很快,不会与其他线程发生冲突;
  4. 在迭代器上不允许进行的元素更改操作(remove、set和add),因为迭代器依赖于不变的数组快照。

适用于List 大小通常很小,只读操作远多于可变操作,并且需要在遍历期间防止线程冲突。

2、原理 / 源码

在⼤多数的应⽤场景中,读操作的⽐例远远⼤于写操作。当执⾏读操作的时候,对数据是没有修改的,所以,⽆须对数据进⾏加锁操作。⽽针对于写操作的场景中,则需要加锁来保证数据的正确性。

因此,CopyOnWriteArrayList基于volatile关键字 + ReentrantLock 实现互斥访问、保证线程安全性。

  • 内部采用数组存储数据,并且数据被volatile修饰,并且在add/set/remove操作是都会新建一个数组,操作数据都体现在新数组中。操作数据完成之后会将新数组的引用赋值给volatile数组。
    • 保存数据的数组array采用volatile修饰,保证了array在线程之间的可见性。当写操作完成后,读操作可以立即感知到新数组的引用。
    • 这也是为什么这个List叫CopyOnWrite的原因。
  • 因为操作数据都需要新建一个数组,所以它的数据操作效率不高;但是在遍历的时候,效率比较高,并且写操作也不会阻塞读的操作
    • 写的过程中,从旧数据中读取;保证写数据的同时不影响读数据操作。
  • 在add/set/remove操作数据时,都会先获取互斥锁ReentrantLock,数据操作完毕之后,再释放互斥锁,以达到线程安全的效果。

1)构造函数

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

    /**
     * 监视器锁
     */
    final transient Object lock = new Object();

    /** 一个缓存数组,使用volatile修饰。 */
    private transient volatile Object[] array;

    /**
     * Gets the array.  Non-private so as to also be accessible
     * from CopyOnWriteArraySet class.
     */
    final Object[] getArray() {
        return array;
    }

    /**
     * Sets the array.
     */
    final void setArray(Object[] a) {
        array = a;
    }
}

public CopyOnWriteArrayList() {
    setArray(new Object[0]);
}

public CopyOnWriteArrayList(Collection<? extends E> c) {
    Object[] elements;
    if (c.getClass() == CopyOnWriteArrayList.class)
        elements = ((CopyOnWriteArrayList<?>)c).getArray();
    else {
        elements = c.toArray();
        if (c.getClass() != java.util.ArrayList.class)
            elements = Arrays.copyOf(elements, elements.length, Object[].class);
    }
    setArray(elements);
}

public CopyOnWriteArrayList(E[] toCopyIn) {
    setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
  • CopyOnWriteArrayList的主体是volatile修饰Object[];线程安全由互斥锁ReentrantLock保证。
  • 并且由于数组也被transient关键字修饰,所以array数组不会被自动序列化。

2、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();
    }
}

方法解析:

  • 执⾏写操作时,⾸先进⾏lock加锁,然后复制原数组创建⼀个⻓度加1的新数组,即:副本数组;
    • 当操作新增操作完毕后,将副本数组替换旧的数组。
    • 由于array是volatile修饰的,所以替换后,array在多线程之间是可⻅的
  • 带来的效果就是:
    • 执⾏写操作的时候,针对的是副本数组;
    • 而读操作,⼀直是针对着原数组;
    • 以此做到写操作不会阻塞读操作;

3)get()

//根据index直接获取数组元素
public E get(int index) {
    return elementAt(getArray(), index);
}

读操作非常简单,就是以不加锁的方式从数组中获取对应下标为index的元素。

4)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();
    }
}

方法解析:

  • 只要操作数组中的数据,都是先加互斥锁ReentrantLock。
  • 如果被删除的是最后一个元素,则直接通过Arrays.copyOf()进行处理,截取旧数组的前array.length - 1,实际也是新建了一个数组。
  • 否则,直接新建一个数组,将旧数组以index为界限,分两块(不包括index位置的数据)拷贝到新数组中。
  • 操作完成时,更新volatile数组的引用,释放互斥锁。

5)iterator()

public Iterator<E> iterator() {
    return new COWIterator<E>(getArray(), 0);
}

基于COWIterator进行遍历。

static final class COWIterator<E> implements ListIterator<E> {
    /** Snapshot of the array */
    private final Object[] snapshot;
    /** Index of element to be returned by subsequent call to next.  */
    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;
    }

    @SuppressWarnings("unchecked")
    public E next() {
        if (! hasNext())
            throw new NoSuchElementException();
        return (E) snapshot[cursor++];
    }

    @SuppressWarnings("unchecked")
    public E previous() {
        if (! hasPrevious())
            throw new NoSuchElementException();
        return (E) snapshot[--cursor];
    }

    public int nextIndex() {
        return cursor;
    }

    public int previousIndex() {
        return cursor-1;
    }

    /**
     * Not supported. Always throws UnsupportedOperationException.
     * @throws UnsupportedOperationException always; {@code remove}
     *         is not supported by this iterator.
     */
    public void remove() {
        throw new UnsupportedOperationException();
    }

    /**
     * Not supported. Always throws UnsupportedOperationException.
     * @throws UnsupportedOperationException always; {@code set}
     *         is not supported by this iterator.
     */
    public void set(E e) {
        throw new UnsupportedOperationException();
    }

    /**
     * Not supported. Always throws UnsupportedOperationException.
     * @throws UnsupportedOperationException always; {@code add}
     *         is not supported by this iterator.
     */
    public void add(E e) {
        throw new UnsupportedOperationException();
    }

    @Override
    public 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;
    }
}

从COWIterator源码可以看出:CopyOnWriteArrayList的迭代器中不支持修改元素的操作。

  • 因为迭代器的remove()、set()、add()操作,COWIterator都会抛出异常UnsupportedOperationException

另外,CopyOnWriteArrayList迭代器不会抛出ConcurrentModificationException异常,因此它不是fail-fast(快速失败),而是fail-safe(安全失败)。

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

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

相关文章

如何在外网访问内网的 Nginx 服务?

计算机业内人士对Nginx 并不陌生&#xff0c;它是一款轻量级的 Web 服务器/反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器&#xff0c;除了nginx外&#xff0c;类似的apache、tomcat、IIS这几种都是主流的中间件。 Nginx 是在 BSD-like 协议下发行的&…

PythonWeb Django PostgreSQL创建Web项目(一)

环境搭建与初始化 一、 python3下载安装测试 1. 下载地址 官网地址&#xff1a;https://www.python.org/ 国内速度比较慢 国内镜像网站地址&#xff1a;https://registry.npmmirror.com/binary.html?pathpython/ 推荐地址速度快 目前官网最新版本3.11.2如下官网截图 要用嘛…

物联网通信复习简记——助力通关期末考试

物联网通信复习简记——助力通关期末考试 文章目录物联网通信复习简记——助力通关期末考试1. 概述1.1 物联网通信体系架构的基本概念1.2 网络分层模型1.3 常见IoT通信技术2. 物理层2.1 编码/基带信号波形/数字基带调制2.2 信道编码-1- 奇偶校验码-2- 汉明码-3- 循环码差错检验…

人员定位需求多,场景目标各不同

GPS技术为现代人带来了许多便利&#xff0c;也提供了诸多基于位置的新型服务。随着科技的发展&#xff0c;人员位置信息在如今的生产生活中也越发重要起来。因此&#xff0c;不同行业领域开始关注人员定位&#xff0c;尤其关注室内人员定位。室内人员定位需求从目的性出发&…

Venom靶机

环境准备 靶机链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;fv06 虚拟机网络链接模式&#xff1a;桥接模式 攻击机系统&#xff1a;kali linux 2022.03 信息收集 1.探测目标靶机开放端口和服务情况 nmap -p- -sV -A 10.10.10.133 2.查看网页最下面&#xf…

GaiaX开源解读 | 表达式作为逻辑动态化的基础,我们是如何设计的

GaiaX跨端模板引擎&#xff0c;是在阿里优酷、淘票票、大麦内广泛使用的Native动态化方案&#xff0c;其核心优势是性能、稳定和易用。本系列文章《GaiaX开源解读》&#xff0c;带大家看看过去三年GaiaX的发展过程。 前言 GaiaX【https://github.com/alibaba/GaiaX】是由优酷应…

详解ArrayList

目录 1.数据结构 2.初始化 2.1.默认构造 2.2.带参构造 3.扩容 3.1.判断需要多少容量 3.2.判断是否需要扩容 3.3.扩容 4.遍历 5.拷贝 6.序列化 JDK版本&#xff1a;JDK8 1.数据结构 底层使用Object类型的数组实现&#xff0c;线程不安全&#xff0c;添加元素时如果内存…

【遇见青山】基于Redis的Feed流实现案例

【遇见青山】基于Redis的Feed流实现案例1.关注推送2.具体代码实现1.关注推送 关注推送也叫做Feed流&#xff0c;直译为投喂。为用户持续的提供"沉浸式”的体验&#xff0c;通过无限下拉刷新获取新的信息。 Feed流产品有两种常见模式&#xff1a; 这里我们实现基本的TimeL…

Python 爬虫工程师面试经验分享,金三银四

&#x1f643; 作为一个 Python 爬虫工程师&#xff0c;我可以分享一些我在面试中的经验和建议。 首先一点是在面试中要表现自信、友好、乐于合作&#xff0c;同时对公司的业务和文化也要有一定的了解和兴趣&#xff0c;这些也是公司在招聘中看重的因素。 文章目录&#x1f55b…

第06章_MySQL多表查询

第06章_多表查询 讲师&#xff1a;尚硅谷-宋红康&#xff08;江湖人称&#xff1a;康师傅&#xff09; 官网&#xff1a;http://www.atguigu.com 多表查询&#xff0c;也称为关联查询&#xff0c;指两个或更多个表一起完成查询操作。 前提条件&#xff1a;这些一起查询的表之…

node.js基于Vue的英语在线学习网站 vscode+mysql

该系统的基本功能包括管理员、学生、教师三个角色功能模块。 对于管理员可以使用的功能模块主要有首页、个人中心&#xff0c;学生管理、教师管理、班级管理、课程管理&#xff0c;在线学习管理、作业管理、试卷管理、试题管理、 在线论坛、系统管理、考试管理等功能。 对于学生…

STM32F765ZIT6中文规格STM32F765ZGT6引脚图 微控制器MCU

说明STM32F7 32 位 MCUFPU 基于高性能的 ARMCortex-M7 32 位 RISC 内核&#xff0c;工作频率高达 216MHz。Cortex-M7 内核具有单浮点单元(SFPU)精度&#xff0c;支持所有 ARM 单精度数据处理指令与数据类型。同时执行全套 DSP 指令和存储保护单元&#xff08;MPU&#xff09;&a…

各CCF期刊点评网站/学术论坛的信息汇总及个人评价

CCF中文期刊投稿选择之篇章一:各CCF期刊点评网站/学术论坛的信息汇总及个人评价中文科技期刊A类&#xff08;EI检索&#xff09;中文期刊投稿点评网站整理1.小木虫学术论坛2. Letpub3. Justscience4. 发表记5. 会伴&#xff08;Conference Partner)6. ijouranl7. 掌桥科研这是以…

Win11的两个实用技巧系列之如何关闭登录密码?

Win11如何关闭登录密码?Win11关闭登录密码的两种解决方法win11是电脑更新后的全新系统&#xff0c;每次开启需要输入密码。有的用户嫌麻烦想要关闭&#xff0c;下面小编就为大家带来了关闭的方法&#xff0c;一起来看看吧有不少用户在升级或者第一次使用Win11系统的时候&#…

uni-app做微信小程序的分包处理

我们的都知道微信小程序有随即随用&#xff0c;用完即走的优点&#xff0c;并且它开发门槛低&#xff0c;但是它也有一个致命的缺点&#xff0c;就是代码包体积的限制&#xff0c;这一缺点让小程序的开发有了一定的限制&#xff0c;现在有一方法可以减少代码包的体积&#xff0…

界面组件Telerik ThemeBuilder R1 2023开创应用主题研发新方式!

Telerik DevCraft包含一个完整的产品栈来构建您下一个Web、移动和桌面应用程序。它使用HTML和每个.NET平台的UI库&#xff0c;加快开发速度。Telerik DevCraft提供最完整的工具箱&#xff0c;用于构建现代和面向未来的业务应用程序&#xff0c;目前提供UI for ASP.NET包含一个完…

汉诺塔递归算法精讲

文章目录前言一、汉诺塔是个啥&#xff1f;二、手动解法三、解法抽象四、递归解法五、总结前言 递归算法是计算机算法中的基础算法&#xff0c;也是非常重要的算法&#xff0c;从某种程度上讲&#xff0c;它有一点儿AI的影子。人脑是可以完成递归思路的&#xff0c;但是对不起…

《爆肝整理》保姆级系列教程python接口自动化(十五)--参数关联接口(详解)

简介 我们用自动化新建任务之后&#xff0c;要想接着对这个新建任务操作&#xff0c;那就需要用参数关联了&#xff0c;新建任务之后会有一个任务的Jenkins-Crumb&#xff0c;获取到这个Jenkins-Crumb&#xff0c;就可以通过传这个任务Jenkins-Crumb继续操作这个新建的任务。 …

自适应布局之淘宝无限适配+rem+微信rpx自适应

一、自适应布局 所谓前端适配&#xff0c;就是为了让移动设计稿在大部分的移动设备上看起来有一致的展示效果&#xff0c;目前比较流行的方法有两种。一种是强制meta viewport宽度为设计稿宽度&#xff0c;一种是使用rem自适应布局的flexible.js。 二、当前流行的移动端自适应…

【刷题笔记】--盛最多水的容器--双指针

题目&#xff1a; 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;你不…