CopyOnWriteArrayList真的线程安全吗?

news2024/9/19 10:48:34

前几天刷博客时,无意中看到一篇名为《CopyOnWriteArrayList真的完全线程安全吗》博客。心中不禁泛起疑问,它就是线程安全的啊,难道还有啥特殊情况?

我们知道CopyOnWrite的核心思想正如其名:写时复制。在对数据有修改操作时,先复制再操作,最后替换原数组。在这些操作时,是有加锁的了。

1 问题复现

这篇博文中主要提到数组越界异常。场景为:假设现在有一个已存在的列表,线程1尝试去查询列表最后一个元素,而此时线程2要去删除列表最后一个元素。此时线程1由于最开始读取的size()=n,在线程2删除后size()=n-1,再拿原Index方式时,便触发ArrayIndexOutOfBoundsException异常。

其实读到这里,我们就已经知道了问题所在。在读取列表大小根据索引访问两个时间点,列表数据已经发生了改变。这种异常理论上属于可预知的异常。

请看下面的代码,并思考下并发执行会有问题吗

CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<>(l);

while (true) {
    if (!cowList.isEmpty()) {
        cowList.remove(0);
    } else {
        return;
    }
}
复制代码

我们不妨来试下。

/**
 * @author lpe234
 * @date 2022/12/03
 */
@Slf4j
public class CowalTest {
    public static void main(String[] args) {
        List<String> l = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            l.add(String.valueOf(i));
        }

        CopyOnWriteArrayList<String> cowList = new CopyOnWriteArrayList<>(l);

        final Runnable rab = () -> {
            while (true) {
                if (!cowList.isEmpty()) {
                    cowList.remove(0);
                } else {
                    return;
                }
            }
        };

        new Thread(rab).start();
        new Thread(rab).start();
    }
}
复制代码

程序执行结果如下:

Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
	at java.base/java.util.concurrent.CopyOnWriteArrayList.elementAt(CopyOnWriteArrayList.java:386)
	at java.base/java.util.concurrent.CopyOnWriteArrayList.remove(CopyOnWriteArrayList.java:478)
	at com.example.other.CowalTest.lambda$main$0(CowalTest.java:25)
	at java.base/java.lang.Thread.run(Thread.java:834)
复制代码

原因就在于cowList.isEmpty()cowList.remove(0)为两个操作。在这两个操作之间,并没有什么机制来保证cowList不会改变。所以出现异常,是可预见的。

2 源码分析

核心属性及get/set方法。

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

    /** 所有涉及到array变更操作的锁。(在内置锁和ReentrantLock都可使用时,我们更倾向于内置锁) */
    final transient Object lock = new Object();

    /** 这个数组的所有访问,只会通过getArray/setArray来进行。 */
    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;
    }
复制代码

可见实现其实很简单。内部使用Object[] array来承载数据。使用volatile来保证多线程下数组的可见性。

再看下isEmptyremove方法。

public int size() {
    return getArray().length;
}

public boolean isEmpty() {
    return size() == 0;
}

public E remove(int index) {
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        E oldValue = elementAt(es, index);
        int numMoved = len - index - 1;
        Object[] newElements;
        if (numMoved == 0)
            newElements = Arrays.copyOf(es, len - 1);
        else {
            newElements = new Object[len - 1];
            System.arraycopy(es, 0, newElements, 0, index);
            System.arraycopy(es, index + 1, newElements, index,
                             numMoved);
        }
        setArray(newElements);
        return oldValue;
    }
}
复制代码

可以很清晰的看到,在这俩方法中,均有getArray()调用。如果中间出现其他线程修改数据,这俩数据必然不一致。在看一个add(E e)方法。

public boolean add(E e) {
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        es = Arrays.copyOf(es, len + 1);
        es[len] = e;
        setArray(es);
        return true;
    }
}
复制代码

此时我们可以很清晰的看清他的编程逻辑。

  • 凡是对数组有修改的操作,先获取锁。
  • 通过getArray()获取数据。前面已加锁,为最新数据,在释放锁前不会有其他线程修改。
  • 对数据进行相关修改操作,Arrays.copyOf是重点。
  • 通过setArray(es)将修改后的数据赋值给原数组。
  • 释放锁。

3 思考

3.1 通过本例我们能学到什么

  • 类似CopyOnWriteArrayList这种并发安全的类,如果不合理(不规范的、错误的)的使用,也会导致并发安全问题
  • 面对事物,要知其然知其所以然。只有了解内部原理,才能更好的去使用它。
  • CopyOnWriteArrayList代码中可以看到,当遇到修改操作时,基本都离不开Arrays.copyOf,这种拷贝会占用额外一倍的内存空间。如果有大量频繁的修改操作,显然是不太合适的。
  • 在修改相关操作代码逻辑中,可以体会到,整体是有那么一点点的延迟的。即一个线程修改完并setArray后,另外的线程才能获取到最新值。

3.2 其他的呢

  • CopyOnWrite是一种很好的思想,它能够使读、写操作并发执行。在Redis的RDB快照生成时,也使用了该思想。
  • 为什么会有final transient Object lock = new Object()这个锁?如果细心看过源码就能明白,其实就是最大程度的减少锁的范围(粒度)。
public boolean addAll(Collection<? extends E> c) {
    Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ?
        ((CopyOnWriteArrayList<?>)c).getArray() : c.toArray();
    if (cs.length == 0)
        return false;
    synchronized (lock) {
		// 略...
	}
}
复制代码

echo '5Y6f5Yib5paH56ugOiDmjpjph5Eo5L2g5oCO5LmI5Zad5aW26Iy25ZWKWzkyMzI0NTQ5NzU1NTA4MF0pL+aAneWQpihscGUyMzQp' | base6

 

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

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

相关文章

只要背着电脑,他可以去任何地方

12月是微软全球开发者月&#xff0c;MSDN 微软开发者社区将在此期间推出特别专栏《技术狂旅》&#xff0c;解读这些技术狂热爱好者的个人经历&#xff0c;循着他们的人生旅程看到我们自己的影子&#xff0c;希望能带给你一些启发或激励&#xff0c;一起探寻自身更多的可能性。 …

Android三种数据存储的方式

文章目录Android数据存储技术持久化技术文件存储将数据存储到文件当中示例_将数据存储到文件当中示例_从文件当中读取数据SharedPreferences存储将数据存储到SharedPreferences1.Context类中getSharedPreferences()方法2.Activity类中的getSharedPreferences()方法往SharedPref…

html+css鼠标悬停发光按钮![HTML鼠标悬停的代码]使用HTML + CSS实现鼠标悬停的一些奇幻效果!

源码如下: <!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title></title> <style> *{ /* 初始化 清除页面元素得内外边距 */ paddin…

【HTML期末作业】大学生抗疫感动专题网页设计作业 抗疫最美逆行者网页 致敬疫情感动人物网页设计制作

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

LIN总线入门

文章目录 前言一、LIN简介1.1 什么是LIN&#xff1f;1.2 为什么要LIN总线1.3 LIN的发展历史1.4 LIN子网1.5 LIN节点1.6 LIN总线特点 二、LIN的协议层2.1 帧结构2.1.1 同步间隔段(Break Field)2.1.2 同步段(Sync Byte Field)2.1.3 受保护 ID 段(Protected Identifier Field)2.1.…

SpringBoot3.x中spring.factories功能被移除的解决方案

背景笔者所在项目组在搭建一个全新项目的时候选用了SpringBoot3.x&#xff0c;项目中应用了很多SpringBoot2.x时代相关的第三方组件例如baomidou出品的mybatis-plus、dynamic-datasource等。在配置好相关依赖、最小启动类和配置之后&#xff0c;发现项目无法启动。于是根据启动…

万字大章_标题、段落、链接、图像等_HTML入门必备基础

万字大章_HTML入门必备基础HTML篇_第四章、HTML基础一、标题二、段落三、链接3.1文本超链接3.2锚点链接3.3功能性链接四、图像4.1 图像标签&#xff08;<img>&#xff09;和源属性&#xff08;Src&#xff09;4.2 alt属性4.3title属性4.4、设置图像4.4.1设置图像的宽度和…

awk命令的使用

1、获取根分区剩余大小 先用df -h命令查看磁盘&#xff0c;确定我们需要获取字段的位置 再使用awk命令获取此字段 df -hdf -h | awk NR6 {print $4}2、获取当前机器ip地址 ifconfig | awk NR2 {print $2}3、统计出apache的access.log中访问量最多的5个IP 使用awk {print $…

Flink CDC-2.3版本概述

问题导读&#xff1a;1、Flink CDC 2.3 版本有哪些重大改进和核心特性&#xff1f; 2、Flink CDC 2.3 版本中MySQL CDC 连接器有哪些优化&#xff1f; 3、Flink CDC 2.4 版本有哪些规划&#xff1f;01 Flink CDC 简介Flink CDC [1] 是基于数据库的日志 CDC 技术&#xff0c;实现…

HTML5期末大作业:基于HTML+CSS+JavaScript校园文化企业网站模板【学生网页设计作业源码】

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

5个拿来就能用的整人代码脚本

「作者主页」&#xff1a;士别三日wyx 「作者简介」&#xff1a;CSDN top100、阿里云博客专家、华为云享专家、网络安全领域优质创作者 整蛊代码脚本一、你的电脑正在被攻击&#xff01;&#xff01;&#xff01;二、CMD炸弹三、无限弹窗四、启动项关机脚本五、舔狗代码一、你的…

毕业后,我的第一辆车

你好&#xff0c;我是阿秀。从学校毕业的时候&#xff0c;我身上差不多有将近12W块钱&#xff0c;有些是自己慢慢存的&#xff0c;有些是在校期间做私活兼职赚的&#xff1a;聊聊我读研期间做过的一些私活和兼职六月毕业后我和我对象去北京玩了几天&#xff0c;去天安门看了毛爷…

微信支付配置

目标&#xff1a;需要生成一个公钥和秘钥配对。放在代码中安全请求。 参考文档&#xff1a;https://pay.weixin.qq.com/wiki/doc/apiv3/wechatpay/wechatpay3_1.shtml https://kf.qq.com/faq/161222NneAJf161222U7fARv.html 一、下载工具 点击下载证书工具&#xff1b;下载…

毕业设计 大数据电商用户行为分析 -python 大数据

文章目录0 前言一.背景描述二.项目背景三.数据来源四.提出问题五.理解数据六.数据清洗6.1缺失值处理6.2查看数据6.3一致化处理6.4查看data_user数据集数据类型&#xff1a;6.5数据类型转换6.6异常值处理七.用户行为分析7.1日访问量分析7.2小时访问量分析7.3不同行为类型用户PV分…

【JavaScript】分支结构和循环结构

目录 一、流程控制 二、分支结构 1. if语句 2. if…else语句 3. if…else if语句 4. switch语句 5. 条件表达式构成的选择结构 三、循环结构 1.while循环 2. do-while循环 3. for循环 3.1 for循环转换为while循环 3.2 断点调试 4. 循环嵌套 一、流程控制 流程控…

知识点2--Docker的安装

上一节知识点说了&#xff0c;一般使用Docker都是在Linux上&#xff0c;Windows有VMware就够了&#xff0c;所以本篇知识点同理带大家在CentOS Linux上安装Docker&#xff0c;但是要知道一个事情&#xff0c;CentOS 6系列的系统由于官方yum的关闭而处于不推荐使用的系统&#x…

微服务框架 SpringCloud微服务架构 16 SpringAMQP 16.9 消息转换器

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式&#xff0c;系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构16 SpringAMQP16.9 消息转换器16.9.1 一个案例16.9.2 消息转换器16.9.3 总…

【笔记】JS的[Object file]类型转string

文件上传&#xff0c;用到若依不分离版&#xff0c;其中文件上传时需要控制文件类型&#xff0c;于是就有了这篇笔记。废话不多说&#xff0c;上代码&#xff1a; var formData new FormData();if ($(#filePath)[0].files[0] null) {$.modal.alertWarning("请先选择文件…

硬盘数据丢失怎么办?电脑硬盘恢复,3个步骤

硬盘是计算机中的一个重要组成部分&#xff0c;是存储数据、进行数据存取的部件。硬盘一旦出现故障&#xff0c;会对电脑系统安全造成威胁&#xff0c;从而导致电脑运行不稳定。硬盘数据丢失后恢复是个难题&#xff0c;电脑硬盘恢复该怎么操作&#xff1f;别急&#xff0c;先来…

欢快畅游的小鱼特效

Jquery 欢快畅游的小鱼特效 index.html <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" con…