多线程篇(其它容器- CopyOnWriteArrayList)(持续更新迭代)

news2025/1/12 7:45:54

一、CopyOnWriteArrayList(一)

1. 简介

并发包中的并发List只有CopyOnWriteArrayList。

CopyOnWriteArrayList是一个线程安全的ArrayList,对其进行的修改操作都是在底层的一个复制的数

组(快照)上进行的,也就是使用了写时复制策略。

CopyOnWriteArraylist的类图结构如图:

在CopyOnWriteArrayList的类图中,每个CopyOnWriteArrayList对象里面有一个array数组对象用来存放具体元素,ReentrantLock独

占锁对象用来保证同时只有一个线程对array进行修改。

CopyOnWriteArrayList使用写时复制的策略来保证list的一致性,而获取 - 修改 - 写入三步操作并不是原子性的,所以在增删改的过程

中都使用了独占锁,来保证在某个时间只有一个线程能对list数组进行修改。

另外CopyOnWriteArrayList提供了弱一致性的迭代器,从而保证在获取迭代器后,其他线程对list的修改是不可见的,迭代器遍历的数

组是一个快照。

2. 独占锁

独占锁是一种思想: 只能有一个线程获取锁,以独占的方式持有锁。和悲观锁、互斥锁同义。

Java中用到的独占锁: synchronized,ReentrantLock。

3. 弱一致性的迭代器

遍历列表元素可以使用迭代器。在讲解什么是迭代器的弱一致性前,先举一个例子来说明如何使用迭代器:

    public static void main(String[] args) {
        CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
        list.add("Hello");
        list.add("World");
        Iterator<String> iterator = list.iterator();

        while (iterator.hasNext()) {
            System.out.println(iterator.next());
        }
    }

运行结果:

迭代器的hasNext方法用于判断列表中是否还有元素,next方法则具体返回元素。

好了,下面来看CopyOnWriteArrayList中迭代器的弱一致性是怎么回事,

所谓弱一致性是指返回迭代器后,其他线程对list的增删改对迭代器是不可见的,下面看看这是如何做到的。

在如上代码中,当调用iterator()方法获取迭代器时实际上会返回一个COWIterator对象,COWIterator对象的snapshot变量保存了当

前list的内容,cursor是遍历list时数据的下标。

为什么说snapshot是list的快照呢?

明明是指针传递的引用啊,而不是副本。如果在该线程使用返回的迭代器遍历元素的过程中,其他线程没有对list进行增删改,那么

snapshot本身就是list的array,因为它们是引用关系。但是如果在遍历期间其他线程对该list进行了增删改,那么snapshot就是快照了,

因为增删改后list里面的数组被新数组替换了,这时候老数组被snapshot引用。这也说明获取迭代器后,使用该迭代器元素时,其他线

程对该list进行的增删改不可见,因为它们操作的是两个不同的数组,这就是弱一致性。

示例:演示多线程下迭代器的弱一致性的效果。

public class Atomic {
    private static final CopyOnWriteArrayList<String> arrayList = new
            CopyOnWriteArrayList<>();

    public static void main(String[] args) throws InterruptedException {
        arrayList.add("hello");
        arrayList.add("alibaba");
        arrayList.add("welcome");
        arrayList.add("to");
        arrayList.add("hangzhou");

        Thread threadOne = new Thread(() -> {
            //修改list中下标为1的元素为baba
            arrayList.set(1, "baba");
            //删除元素
            arrayList.remove(2);
            arrayList.remove(3);
        });

        //保证在修改线程启动前获取迭代器
        Iterator<String> itr = arrayList.iterator();
        threadOne.start();
        // 保证threadOne的run方法执行完毕(完成对arrayList的修改)
        Thread.sleep(1000);
        while (itr.hasNext()) System.out.println(itr.next());
    }
}

运行结果:

在如上代码中,main函数首先初始化了arrayList,然后在启动线程前获取到了arrayList迭代器。

子线程threadOne启动后首先修改了arrayList的第一个元素的值,然后删除了arrayList中下标为2和3的元素。

主线程在子线程执行完毕后使用获取的迭代器遍历数组元素,从输出结果我们知道,在子线程里面进行的操作一个都没有生效,这就是迭

代器弱一致性的体现。

需要注意的是,获取迭代器的操作必须在子线程操作之前进行。

二、CopyOnWriteArrayList(二)

1. 简介

在 ArrayList 的类注释上,JDK 就提醒了我们,如果要把 ArrayList 作为共享变量的话,是线程不安全的,推荐我们自己加锁或者使用

Collections.synchronizedList 方法,其实 JDK 还提供了另外一种线程安全的 List,叫做 CopyOnWriteArrayList

2. 原理

很多时候,我们的系统应对的都是读多写少的并发场景。CopyOnWriteArrayList容器允许并发读,读操作是无锁的,性能较高。至于写

操作,比如向容器中添加一个元素,则首先将当前容器复制一份,然后在新副本上执行写操作,结束之后再将原容器的引用指向新容器。

  • 线程安全的,多线程环境下可以直接使用,无需加锁;
  • 通过锁 + 数组拷贝 + volatile 关键字保证了线程安全;
  • 每次数组操作,都会把数组拷贝一份出来,在新数组上进行操作,操作成功之后再赋值回去。

从整体架构上来说,CopyOnWriteArrayList 数据结构和 ArrayList 是一致的,底层是个数组,只不过 CopyOnWriteArrayList 在对数组进

行操作的时候,基本会分四步走:

  • 加锁;
  • 从原数组中拷贝出新数组;
  • 在新数组上进行操作,并把新数组赋值给数组容器;
  • 解锁

除了加锁之外,CopyOnWriteArrayList 的底层数组还被 volatile 关键字修饰,意思是一旦数组被修改,其它线程立马能够感知到,

代码如下:

private transient volatile Object[] array;

整体上来说,CopyOnWriteArrayList 就是利用锁 + 数组拷贝 + volatile 关键字保证了 List 的线程安全。

3. 优点

读操作(不加锁)性能很高,因为无需任何同步措施,比较适用于读多写少的并发场景。Java的list在遍历时,若中途有别的线程对list容

器进行修改,则会抛ConcurrentModificationException异常。而CopyOnWriteArrayList由于其"读写分离"的思想,遍历和修改操作分别

作用在不同的list容器,所以在使用迭代器进行遍历时候,也就不会抛出ConcurrentModificationException异常了。

4. 缺点

一是内存占用问题,毕竟每次执行写操作都要将原容器拷贝一份。数据量大时,对内存压力较大,可能会引起频繁GC;

二是无法保证实时性,因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新

写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所

以有两份对象内存)。

5. 源码分析

添加操作

    public boolean add(E e) {
        //ReentrantLock加锁,保证线程安全
        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();
        }
    }

添加的逻辑很简单,先将原容器copy一份,然后在新副本上执行写操作,之后再切换引用。当然此过程是要加锁的。

删除操作

    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)
                //如果要删除的是列表末端数据,拷贝前len-1个数据到新副本上,再切换引用
                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();
        }
    }

删除操作同理,将除要删除元素之外的其他元素拷贝到新副本中,然后切换引用,将原容器引用指向新副本。同属写操作,需要加锁。

我们再来看看读操作,CopyOnWriteArrayList的读操作是不用加锁的,性能很高。

    public E get(int index) {
        return get(getArray(), index);
    }

直接读取即可,无需加锁

     private E get(Object[] a, int index) {
        return (E) a[index];
    }

弱一致性的迭代器

所谓弱一致性是指返回迭代器后,其他线程对list的增删改查对迭代器是不可见的

// 演示多线程下迭代器的弱一致性结果
public class copylist {
    private static volatile CopyOnWriteArrayList<String> arrayList = new CopyOnWriteArrayList<>();
    public static void main(String[] args) throws InterruptedException {
        arrayList.add("hello");
        arrayList.add("alibaba");
        arrayList.add("welcome");
        arrayList.add("to");
        arrayList.add("hangzhou");
 
        Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                // 修改list中下标为1的元素为ali
                arrayList.set(1, "ali");
                // 删除元素
                arrayList.remove(2);
                arrayList.remove(3);
            }
        });
        // 保证在修改线程启动前获取迭代器
        Iterator<String> itr = arrayList.iterator();
        // 启动线程
        threadOne.start();
        // 等待子线程执行完毕
        threadOne.join();
        while(itr.hasNext()) {
            System.out.println(itr.next());
        }
    }
}

执行程序:

hello
alibaba
welcome
to
hangzhou

Process finished with exit code 0

执行程序:

hello
alibaba
welcome
to
hangzhou

Process finished with exit code 0

从输出结果我们知道,在子线程里面进行的操作一个都没有生效,这就是迭代器弱一致性的体现。

需要注意的是,获取迭代器的操作必须在子线程操作之前进行。

6. ArrayList转为线程安全的方法

List list = Collections.synchronizedList(new ArrayList());

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

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

相关文章

解决 git 不是内部或外部命令,也不是可运行的程序

目录 报错提示&#xff1a; 一、解决办法 1、从git官网下载windows版本的git 2、安装 3、注意事项 二、报错 1、解决 fatal: Not a git repository (or any of the parent directories): .git 问题 报错提示&#xff1a; 一、解决办法 Windows下配置Git&#xff1a; 1…

sap公司间交易(采购单转储)-公司间交易价格的配置

sap 公司间交易(采购单转储)-公司间交易价格的配置 对于通过采购单转储方式实现公司间交易,公司间交易价系统标准设计是,购货方采用采购单上的单价做为发票校验的价格,而销售方由于没有销售单,则采用的是在 vk11 里维护的公司间售价,这种做法的坏处是经常要同步这两个价格…

error C2275: 将此类型用作表达式非法-解决方案

最近在进行将C移植C的项目&#xff0c;代码改完&#xff0c;生成解决方案时&#xff0c;出现如下错误&#xff1a; 在移植c代码到c的时候&#xff0c;经常会出现一个奇怪的错误&#xff1a;“error C2275: “xxxxx”: 将此类型用作表达式非法” 两个错误属于同一类型&#xff…

投资一家无人机培训机构技术详解

无人机培训机构是随着无人机技术的快速发展和普及而兴起的一种专业培训机构。这类机构专注于为学员提供无人机相关的理论知识、操控技能以及应用技术培训&#xff0c;以满足不同领域对无人机人才的需求。 1. 市场调研与定位 市场调研 在投资无人机培训机构之前&#xff0c;深…

OpenWRT有三个地方设置DNS,究竟设置哪个地方会更好?

前言 刚上手OpenWRT软路由系统的小伙伴或许都会有这样的疑问&#xff1a;OpenWRT这个系统有三个地方是设置DNS的&#xff0c;究竟设置哪一个才是正确的&#xff1f; 这个还得从实际应用说起。 一般来说&#xff0c;咱们在使用路由器的时候&#xff0c;DNS都是默认运营商的DN…

YOLOV3实现越界检测——智能安防

目录 应用前景 1. 安全监控系统 2. 家庭安防系统 3. 无人机监控 4. 交通管理 5. 无人驾驶技术 6. 大型活动现场 代码说明 1. YOLO 模型加载 2. 摄像头视频流捕获 3. 安全区域绘制 4. YOLOv3 目标检测 5. 过滤和标记人类目标 6. 入侵检测 7. 结果显示和退出 总结…

黑神话怎么录?游戏录屏必备,探索2024年7款游戏录屏软件排行榜

在游戏世界中&#xff0c;记录下每一个精彩瞬间已成为玩家们的共同需求。2024年&#xff0c;随着《黑悟空神话》等大作的问世&#xff0c;玩家们对于游戏录屏软件的需求愈发高涨。本文将为您推荐几款在2024年备受好评的游戏录屏软件&#xff0c;帮助您捕捉游戏中的每一个高光时…

NLP-文本分类文献阅读-前置基础-词汇解释-通俗易懂-9月份-学习总结

目录 迁移学习 特征选择 特征工程 朴素贝叶斯分类方法 支持向量机 K-最近邻&#xff08;K-Nearest Neighbors, KNN&#xff09; 特征向量稀疏 卷积神经网络 循环神经网络 图神经网络 TextCNN 动态 K 最大池化 One-hot BOW Word2vec 池化&#xff08;Pooling&#xff09; 全连接…

Python画笔案例-043 绘制“篱笆“

1、绘制 “篱笆” 通过 python 的turtle 库绘制 “篱笆”&#xff0c;如下图&#xff1a; 2、实现代码 绘制 “篱笆”&#xff0c;以下为实现代码&#xff1a; """篱笆.py """ import turtledef draw_triangle():"""画正三角形函…

万物皆AI:联发科技 Genio 130 与 ChatGPT 的火花 - 基于 MTK Genio 130 结合 ChatGPT 功能的解决方案

随着人工智慧(AI)在2022-2023的爆炸性成长&#xff0c;我们迎来了AI世代&#xff0c;无论是交通、工业、金融、制造、医疗等各领域&#xff0c;AI已被广泛的应用来解决各类问题与加速发展。伴随著AI走入我们的生活&#xff0c;我们也在所拥有的各类智慧装置上&#xff0c;看到各…

农田杂草检测数据集 2900张 杂草检测 带标注 voc yolo

这是一个关于农田杂草的图像识别数据集&#xff0c;包含了2900张带标注的图片&#xff0c;可以用于训练计算机视觉算法&#xff0c;如VOC或YOLO等目标检测模型。 该数据集中包含以下10类农田杂草&#xff1a; Carpetweeds&#xff08;450张&#xff09;Crabgrass&#xff08;1…

基于JavaWeb开发的Java+SpringMvc+vue+element实现上海汽车博物馆平台

基于JavaWeb开发的JavaSpringMvcvueelement实现上海汽车博物馆平台 &#x1f345; 作者主页 网顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; &#x1f345; 查看下方微信号获取联系方式 承接…

GIS数据采集软件:地理信息与遥感技术的智慧之眼

在信息时代&#xff0c;数据如同血液&#xff0c;滋养着各行各业的创新与进步&#xff0c;而地理信息与遥感领域中的数据采集软件&#xff0c;正是这股生命之源&#xff0c;它不仅为科学研究、城市规划、环境保护、灾害监测、资源管理等提供了精确数据支持&#xff0c;更是智慧…

Qt发送http请求

需要在.pro项目文件中添加网络模块&#xff0c;不然无法使用http相关的类获取数据 QT core gui network 用于http通信的类为QNetworkAccessManager 发送Get请求 通过调用QNetworkAccessManager::get方法 QNetworkReply *QNetworkAccessManager::get(const QNetworkReques…

跨部门SOP与统一知识库:打破信息孤岛,促进团队协作

引言&#xff1a; 在当今这个快速变化且高度竞争的商业环境中&#xff0c;企业面临着前所未有的挑战&#xff0c;其中之一便是如何高效地跨越部门界限&#xff0c;实现无缝协作。传统的组织结构往往导致信息孤岛的出现&#xff0c;不同部门间流程不一致、信息不共享&#xff0…

系统架构师考试学习笔记第五篇——架构设计补充知识(23)知识产权

本章考点&#xff1a; 第23课时主要学习国家与行业标准、知识产权的内容。根据考试大纲,本课时知识点会涉及单项选择题,按以往全国计算机技术与软件专业技术资格(水平)考试的出题规律约占3分。本课时内容属于补充知识范畴,考题类型固定。本课时知识架构如图23.1所示。 一、知识…

Python(PyTorch)和MATLAB及Rust和C++结构相似度指数测量导图

&#x1f3af;要点 量化检查图像压缩质量低分辨率多光谱和高分辨率图像实现超分辨率分析图像质量图像索引/多尺度结构相似度指数和光谱角映射器及视觉信息保真度多种指标峰值信噪比和结构相似度指数测量结构相似性图像分类PNG和JPEG图像相似性近似算法图像压缩&#xff0c;视频…

Linux服务器中在指定python环境中新建notebook

在指定环境下建立新的IPython内核pip install ipykernel生成ipykernel的配置文件&#xff1a;python -m ipykernel install --user --name netCLR 其中--user是指在用户级别生成&#xff0c;如果没有--user就会在系统中安装&#xff0c;如果不是管理员的话就会没有权限生成&…

【STM32】TIM输入捕获测量电平持续时间

本篇博客重点在于标准库函数的理解与使用&#xff0c;搭建一个框架便于快速开发 目录 前言 测量电平时间思路 配置中断 中断服务函数 捕获完成判断 代码示例 IC.h IC.c main.c 代码参考 前言 阅读本篇博客需了解定时器时基单元&#xff0c;更新中断和输入捕获 定时…

UWB定位系统在智能制造中的应用实践

UWB(Ultra-Wideband&#xff0c;超宽带)定位系统在智能制造中的应用实践已经取得了显著成效。以下是对其应用实践的详细阐述&#xff1a; 一、高精度定位与实时监控 高精度定位&#xff1a;UWB技术以其厘米级甚至毫米级的定位精度&#xff0c;远超传统的GPS和Wi-Fi等定位技术&a…