CopyOnWriteArrayList使用以及原理分析

news2025/2/23 23:18:28

文章目录

  • 一、CopyOnWriteArrayList的简介
  • 二、CopyOnWriteArrayList类的继承关系
    • 1、Iterable接口:
    • 2、Collection接口:
    • 3、List接口:
    • 4、Cloneable接口:
    • 5、Serializable接口:
    • 6、RandomAccess接口:
  • 三、CopyOnWriteArrayList的基本使用
    • 1. 使用场景:
      • 1.1、读多写少的情况:
      • 1.2、数据一致性要求较低:
      • 1.3、需要遍历操作:
    • 2. 代码实现:
    • 3. 运行结果:
    • 4. 案例分析:
  • 四、CopyOnWriteArrayList的优缺点
    • 1. 优点:
      • 1.1、线程安全:
      • 1.2、并发读取性能好:
      • 1.3、可用于读多写少的场景:
    • 2. 缺点:
      • 2.1、内存占用高:
      • 2.2、写操作的延迟高:
      • 2.3、不适用于迭代器的弱一致性需求:
    • 3. 总结:
  • 五、底层原理分析
    • 1. add方法
    • 2. get方法

一、CopyOnWriteArrayList的简介

copyonwriteArrayList

  1. CopyOnWriteArrayList是Java中的一种并发集合类,它实现了List接口,并且是线程安全的。它的特点是在进行写操作时,会创建一个新的副本来进行操作,而不是直接修改原有的集合。

  2. CopyOnWriteArrayList的实现机制是在写操作时,先将原有的集合进行复制,然后对新的副本进行修改操作,最后将修改后的副本替换原有的集合。这样可以保证在写操作时不会影响到正在进行读操作的线程,保证了读写的并发性。

  3. 由于每次写操作都会复制整个集合,所以CopyOnWriteArrayList的写操作性能较低,适用于读操作较多而写操作较少的场景。它适合用于一些读多写少的并发场景,比如缓存、观察者模式等。

  4. 需要注意的是,CopyOnWriteArrayList虽然是线程安全的,但是它的迭代器并不支持修改操作,如果需要对集合进行修改,需要使用特殊的方法来操作。此外,由于每次写操作都会创建一个新的副本,所以CopyOnWriteArrayList占用的内存较大,需要根据具体场景进行权衡。

二、CopyOnWriteArrayList类的继承关系

1、Iterable接口:

CopyOnWriteArrayList类实现了Iterable接口,使得它可以被迭代遍历。通过实现iterator()方法,可以获取一个迭代器,用于遍历集合中的元素。

2、Collection接口:

CopyOnWriteArrayList类继承了AbstractCollection类,间接实现了Collection接口。通过实现Collection接口,CopyOnWriteArrayList类获得了一些常用的集合操作方法,比如判断是否包含某个元素、计算集合的大小等。

3、List接口:

CopyOnWriteArrayList类间接实现了List接口,通过继承AbstractList类实现了List接口的一些方法。List接口定义了有序的集合,CopyOnWriteArrayList类通过继承List接口,可以按照索引进行访问和操作集合中的元素。

4、Cloneable接口:

CopyOnWriteArrayList类实现了Cloneable接口,使得它可以进行克隆操作。通过实现clone()方法,可以创建CopyOnWriteArrayList对象的副本。

5、Serializable接口:

CopyOnWriteArrayList类实现了Serializable接口,使得它可以进行序列化操作。通过实现writeObject()和readObject()方法,可以将CopyOnWriteArrayList对象转换为字节流,并进行存储或传输。

6、RandomAccess接口:

CopyOnWriteArrayList类实现了RandomAccess接口,表示它可以高效地随机访问元素。RandomAccess接口是一个标记接口,用于标识实现该接口的类可以通过索引进行快速随机访问,CopyOnWriteArrayList类通过实现该接口,表明它可以高效地随机访问元素。

三、CopyOnWriteArrayList的基本使用

1. 使用场景:

CopyOnWriteArrayList的主要使用场景是在多线程环境下,需要读多写少的情况下使用。

由于CopyOnWriteArrayList是线程安全的,它通过在写操作时创建一个新的数组来实现并发安全。在写操作时,原有数组不发生改变,而是将写操作应用于新的数组。这样就避免了读操作和写操作的冲突,保证了线程安全。

CopyOnWriteArrayList适用于以下场景:

1.1、读多写少的情况:

由于写操作会创建新的数组,因此写操作的开销较大。如果写操作很频繁,会导致性能下降。因此,在读多写少的情况下,CopyOnWriteArrayList能够提供较好的性能。

1.2、数据一致性要求较低:

由于写操作不会影响原来的数组,读操作总是读取最新的数组。因此,如果对数据的实时性要求不高,可以使用CopyOnWriteArrayList。

1.3、需要遍历操作:

CopyOnWriteArrayList适合于需要频繁进行遍历操作的场景。由于读操作不需要加锁,所以可以并发地进行遍历操作,提高了遍历的效率。

需要注意的是,CopyOnWriteArrayList适用于静态数据,即数据一旦初始化就不会修改的情况。如果数据需要频繁修改,则不适合使用CopyOnWriteArrayList。此外,由于每次写操作都会创建一个新的数组,占用内存较大,所以不适合数据量过大的情况。

2. 代码实现:

import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteArrayListDemo {
    public static void main(String[] args) {
        // 创建一个CopyOnWriteArrayList对象
        CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();

        // 添加元素
        list.add(1);
        list.add(2);
        list.add(3);

        // 创建一个线程用于遍历元素
        Thread iteratorThread = new Thread(() -> {
            Iterator<Integer> iterator = list.iterator();
            while (iterator.hasNext()) {
                System.out.println(iterator.next());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        // 创建一个线程用于修改元素
        Thread modifyThread = new Thread(() -> {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            list.add(4);
            list.remove(1);
        });

        // 启动两个线程
        iteratorThread.start();
        modifyThread.start();
    }
}

3. 运行结果:

运行结果

4. 案例分析:

  1. 在上面的代码中,我们首先创建了一个CopyOnWriteArrayList对象,并向其中添加了一些元素。然后,我们创建了两个线程:一个线程用于遍历元素,另一个线程用于修改元素。在遍历线程中,我们使用iterator()方法获取迭代器,并通过调用next()方法来遍历元素。在修改线程中,我们在添加和删除元素之间添加了一个延迟,以便观察到遍历线程的输出结果。

  2. 由于CopyOnWriteArrayList是线程安全的,即使在遍历的同时进行元素的修改,也不会抛出ConcurrentModificationException异常。这是因为在修改操作时,CopyOnWriteArrayList会创建一个新的数组来存储修改后的元素,而原来的数组不会受到影响。因此,遍历线程可以继续使用原来的数组进行遍历,保证了遍历的安全性。

四、CopyOnWriteArrayList的优缺点

1. 优点:

1.1、线程安全:

CopyOnWriteArrayList是线程安全的,多个线程可以同时读取列表中的数据而无需额外的同步机制。

1.2、并发读取性能好:

由于读操作不需要加锁,多个线程可以同时读取,提高了并发读取的性能。

1.3、可用于读多写少的场景:

适用于读操作频繁,写操作较少的场景。

2. 缺点:

2.1、内存占用高:

每次写操作都会创建一个新的数组,导致内存占用较高。如果写操作频繁,会消耗大量的内存。

2.2、写操作的延迟高:

由于每次写操作都会复制整个数组,写操作的执行时间较长,可能会导致写操作的延迟较高。

2.3、不适用于迭代器的弱一致性需求:

CopyOnWriteArrayList的迭代器是弱一致性的,即迭代器在创建之后不会反映后续的修改操作。这可能会导致迭代器在遍历过程中出现数据不一致的情况。

3. 总结:

综上所述,CopyOnWriteArrayList适用于读多写少的场景,对于需要高并发性能和线程安全的读操作非常有用。然而,它的内存占用高和写操作的延迟高可能会影响性能,在弱一致性需求较高的情况下也不适合使用。

五、底层原理分析

1. 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();
        }
    }
  1. 获取一把独占多lock ,加锁
  2. 获取原数组长度,创建一个原数组长度+1的新数组
  3. 利用Arrays.copyOf将元素进行复制到新的数组
  4. 将添加的元素设置到新开辟数组的最后一个位置
  5. 修改老的数组的引用指向
  6. 释放锁

2. get方法

    public E get(int index) {
        return get(getArray(), index);
    }
    private E get(Object[] a, int index) {
        return (E) a[index];
    }
  1. 根据下标获取指定位置元素,可以看到读不加锁

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

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

相关文章

模糊图片怎么修复清晰度?这几个方法分享给你~

在我们的日常生活和工作中&#xff0c;经常会遇到图片模糊的问题&#xff0c;这可能是由于拍摄时的手抖、对焦不准确或者图片压缩过度等原因造成的。那么&#xff0c;如何修复模糊的图片&#xff0c;提高其清晰度呢&#xff1f;本文将为您介绍几种方法。 方法一&#xff1a;使…

ABAP调用阿里云接口-短信服务-HTTP协议及签名(abap版本)<转载>

原文链接&#xff1a;https://blog.csdn.net/xiefireworks/article/details/113037650 阿里云接口文档请参考官网地址 https://help.aliyun.com/document_detail/59210.html?spm5176.8195934.J_5834642020.5.11ba4378DLVi4O 此处仅介绍使用ABAP完成阿里云短信服务签名请求的…

树莓派4B的串口UART配置

1 安装串口&#xff1a; 如果没有更换pip源会报错&#xff0c;所以指定安装源 pip install pyserial -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com 修改uart配置&#xff1a; vim /boot/firmware/config.txt 在末尾添加&#xff1a; dtoverlayuart2…

X86架构的Linux(Ubuntu版本)上离线安装CUnit来解决Could not find CUnit(missing:CUNIT_LIBRARY)问题

前言1 下载cunit压缩安装包&#xff1a;CUint-2.1-3.tar.bz2&#xff08;为了安装成功请下载对应版本&#xff09;2 解压安装压缩包3 sudo ./bootstrap --prefix/usr/local/cunit 生成可执行文件configure*4 sudo ./configure --prefix/usr/local/cunit5 sudo make . 编译 &…

Centos安装指定docker版本和docker-compose

目录 一. 直接安装Docker最新镜像源 1. 卸载旧版本的Docker&#xff1a; 2. 安装依赖包&#xff1a; 3. 添加Docker源&#xff1a; 4. 安装Docker&#xff1a; 5. 启动Docker服务&#xff1a; 6. 验证Docker是否安装成功&#xff1a; 二、指定Docker版本安装 1. 查看…

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

已解决&#xff08;MongoDB安装报错&#xff09;‘mongo’ 不是内部或外部命令,也不是可运行的程序 报错代码 粉丝群里的一个小伙伴安装完MongoDB后&#xff0c;在cmd中启动&#xff0c;却说不是可运行的命令&#xff1f; 报错原因 报错原因&#xff1a;由于没有配置环境变量的…

【Redis】4、全局唯一 ID生成、单机(非分布式)情况下的秒杀和一人一单

目录 一、利用 Redis 实现全局唯一 ID 生成(1) 为啥要用全局唯一 ID 生成(2) 全局唯一 ID 生成器(3) 全局 ID 的结构(4) 代码实现① RedisIdWorker② Test (5) 全局唯一 ID 其他生成策略 二、添加优惠券(1) 数据库(2) 添加优惠券接口 三、优惠券秒杀下单功能(1) 超卖问题(2) 乐…

项目上线“G”速报 | GBASE助力四川银行反洗钱系统上线运行

随着金融机构资管业务的不断发展&#xff0c;藉由以银行为代表的金融机构建设反洗钱系统&#xff0c;向执法机构报送可疑活动&#xff0c;成为侦测潜在金融犯罪、打击腐败的重要防线。为更好助力反洗钱工作&#xff0c;四川银行着手构建新一代的反洗钱系统。作为信创第二期的重…

重磅预告丨Fortinet Demo Day系列实战攻防演练来袭!

随着网络安全形势的日趋严峻&#xff0c;越来越多的企业遭受了勒索、欺诈等危害。在高昂的赎金、生产损失&#xff0c;以及名誉损害的恐惧中&#xff0c;企业已经谈“黑”色变。黑客是如何悄无声息的“越过”重重高墙、道道壁垒进入到生产环境、办公空间&#xff0c;并在内网疯…

Android各种支持裤的最新依赖以及用户文档

https://developer.android.com/jetpack/androidx/versions 链接截图如下&#xff1a; 点击“Release Notes”中的链接&#xff0c;如果对应支持库有用户指南还能看到对应链接&#xff0c;还有如何添加依赖等&#xff0c;比如支持库中的actviity&#xff0c;如下&#xff1a;截…

【漂移-扩散通量重建 FV 方案】用于半导体和气体放电模拟的电子传输的更准确的 Sharfetter-Gummel 算法(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Perl 7 - 使用 Perlbrew 管理perl 版本

文章目录 关于 Perlbrew安装 Perlbrew使用 perlbrew 安装/管理 perl 版本 关于 Perlbrew 官网&#xff1a;https://perlbrew.pl 相关文档&#xff1a; App::perlbrew https://metacpan.org/pod/App::perlbrew Perlbrew 是一个工具&#xff0c;用于管理您$HOME 目录(或您指定的…

Flink基于信用值的流量控制

背景 flink内部实现了一个类似于tcp滑动窗口概念的流量控制功能&#xff0c;以满足其内部的流量控制功能&#xff0c;本文就来讲解下flink实现的基于信用值的流量控制的原理 实现原理 首先&#xff0c;我们先来看一下在flink中是如何实现数据传输的&#xff0c; 从上图可知&…

css animation 鼠标移入暂停会抖动

如图 实现一个赞助商横向滚动列表墙&#xff0c; 上下两排向右滚动&#xff0c;中间向左滚动&#xff0c;鼠标移入暂停当前行。 实现&#xff1a; // 使用animation.moving {animation: move 20s linear infinite; }keyframes move {0% {}100% {transform: translateX(-50%);…

可靠的手机问题修复工具分享 - 修复各种 Android 系统问题

一般来说&#xff0c;安卓手机都可以流畅运行。但不幸的是&#xff0c;有时您的Android手机可能无法正常运行&#xff0c;例如无响应、突然重启等。在这种情况下&#xff0c;您将需要Android手机维修软件。这些 Android 修复工具可以帮助您轻松解决此类问题&#xff0c;并还给您…

QEMU源码全解析4 —— QEMU参数解析(4)

接前一篇文章&#xff1a;QEMU源码全解析3 —— QEMU参数解析&#xff08;3&#xff09; 本文内容参考&#xff1a; 《趣谈Linux操作系统》 —— 刘超&#xff0c;极客时间 《QEMU/KVM》源码解析与应用 —— 李强&#xff0c;机械工业出版社 特此致谢&#xff01; 本回讲解Q…

JavaWeb学生考勤签到请假系统

一、项目简介 本项目是一套JavaWeb学生考勤签到请假系统&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Java学习者。 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;ecl…

生成图片验证码-Google Kaptcha

CaptchaImage生成 验证码 图片 captchaProducerMath.createText() 类似 captchaProducer.createText() 混合带字符的char如下 从若依学的&#xff0c;先看他的引用方式 package com.ruoyi.web.controller.common;import java.awt.image.BufferedImage; import java.io.IOExcept…

【导航算路(RP)模块功能】

什么是RP Route Production/Route Planning 就是在给定自车位置和目的地的情况下&#xff0c;按照用户设定的不同条件&#xff0c;计算出一条或多条从自车位置到目的地的花费(根据用户的设定&#xff0c;可能是指时间&#xff0c;费用等)最少的最优路以供用户使用。 为什么要…

【新版系统架构】第十七章-通信系统架构设计理论与实践

软考-系统架构设计师知识点提炼-系统架构设计师教程&#xff08;第2版&#xff09; 第一章-绪论第二章-计算机系统基础知识&#xff08;一&#xff09;第二章-计算机系统基础知识&#xff08;二&#xff09;第三章-信息系统基础知识第四章-信息安全技术基础知识第五章-软件工程…