CopyOnWriterArrayList

2023-05-16

CopyOnWrite

  • CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
  • 那为什么不直接修改,而是要拷贝一份修改呢?
    这是为了在“读”的时候不加锁。(以空间换时间的策略)
  • 为了提升读取的效率,修改时不在原数据上修改,而是在复制的数组上修改,改完之后再设置回来,这样做就不会阻塞读的线程

CopyOnWriteArrayList

  • CopyOnWriteArrayList介绍
  1. CopyOnWriteArrayList,写数组的拷贝,支持高效率并发且是线程安全的,读操作无锁的ArrayList。所有可变操作都是通过对底层数组进行一次新的复制来实现。
  2. CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里,比如缓存。它不存在扩容的概念,每次写操作都要复制一个副本,在副本的基础上修改后改变Array引用。CopyOnWriteArrayList中写操作需要大面积复制数组,所以性能肯定很差。
  3. CopyOnWriteArrayList 合适读多写少的场景,不过这类慎用 ,因为谁也没法保证CopyOnWriteArrayList 到底要放置多少数据,万一数据稍微有点多,每次add/set都要重新复制数组,这个代价实在太高昂了。在高性能的互联网应用中,这种操作分分钟引起故障。
  • CopyOnWriteArrayList 缺点:
  1. 内存占用问题。因为CopyOnWrite的写时复制机制,所以在进行写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)。如果这些对象占用的内存比较大,比如说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC。之前我们系统中使用了一个服务由于每晚使用CopyOnWrite机制更新大对象,造成了每晚15秒的FullGC,应用响应时间也随之变长。
    针对内存占用问题,可以通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制。或者不使用CopyOnWrite容器,而使用其他的并发容器,如ConcurrentHashMap。
  2. 数据一致性问题。CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器。

在这里插入图片描述

CopyOnWriteArrayList的核心数据结构是一个数组,代码如下:

public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess,Cloneable, java.io.Serializable {
    final transient ReentrantLock lock = new ReentrantLock();

    /** 数组,只能通过 getArray/setArray 访问,加上transient不让其被序列化,加上volatile修饰来保证多线程下的其可见性和有序性 */
    private transient volatile Object[] array;
}

构造函数

    public CopyOnWriteArrayList() {
       //默认创建一个大小为0的数组
        setArray(new Object[0]);
    }
	//setArray只是换了一下引用地址
    final void setArray(Object[] a) {
        array = a;
    }
    final Object[] getArray() {return array;}
	
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        //如果当前集合是CopyOnWriteArrayList的类型的话,直接赋值给它
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
         	//否则调用toArra()将其转为数组   
            elements = c.toArray();
            // c.toArray might (incorrectly) not return Object[] (see 6260652)
            if (elements.getClass() != Object[].class)
                elements = Arrays.copyOf(elements, elements.length, Object[].class);
        }
        //设置数组
        setArray(elements);
    }
	
    public CopyOnWriteArrayList(E[] toCopyIn) {
        //将传进来的数组元素拷贝给当前数组
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }

下面是CopyOnArrayList的几个“读”方法:

final Object[] getArray() {
	return array;
}
//
public E get(int index) {
	return elementAt(getArray(), index);
}
public boolean isEmpty() {
	return size() == 0;
}
public boolean contains(Object o) {
	return indexOf(o) >= 0;
}
public int indexOf(Object o) {
	Object[] es = getArray();
	return indexOfRange(o, es, 0, es.length);
}
private static int indexOfRange(Object o, Object[] es, int from, int to) {
	if (o == null) {
		for (int i = from; i < to; i++)
			if (es[i] == null)
				return i;
	} else {
		for (int i = from; i < to; i++)
			if (o.equals(es[i]))
				return i;
	}
	return -1;
}

既然这些“读”方法都没有加锁,那么是如何保证“线程安全”呢?答案在“写”方法里面。
add

public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
	// 锁对象
	final transient Object lock = new Object();
	//数据加在数组后面
	public boolean add(E e) {
        //使用ReentrantLock上锁
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            //调用getArray()获取原来的数组
            Object[] elements = getArray();
            int len = elements.length;
            //复制老数组,得到一个长度+1的数组
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //添加元素,在用setArray()函数替换原数组
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }
	//在指定索引处添加数据
	public void add(int index, E element) {
		synchronized (lock) { // 同步锁对象
			Object[] es = getArray();
			int len = es.length;
			if (index > len || index < 0)
				throw new IndexOutOfBoundsException(outOfBounds(index,len));
			Object[] newElements;
			//需要向后移动的元素
			int numMoved = len - index;
			if (numMoved == 0)
				newElements = Arrays.copyOf(es, len + 1);
			else {
				newElements = new Object[len + 1];
				//CopyOnWrite,写的时候,先拷贝一份之前的数组。
				System.arraycopy(es, 0, newElements, 0, index); 
				System.arraycopy(es, index, newElements, index + 1,numMoved);
			}
			newElements[index] = element;
			// 把新数组赋值给老数组
			setArray(newElements); 
		}
	}
}

remove

    public boolean remove(Object o) {
        Object[] snapshot = getArray();
        int index = indexOf(o, snapshot, 0, snapshot.length);
        return (index < 0) ? false : remove(o, snapshot, index);
    }

    private boolean remove(Object o, Object[] snapshot, int index) {
        final ReentrantLock lock = this.lock;
        //上锁
        lock.lock();
        try {
            Object[] current = getArray();
            int len = current.length;
            if (snapshot != current) findIndex: {
                int prefix = Math.min(index, len);
                for (int i = 0; i < prefix; i++) {
                    if (current[i] != snapshot[i] && eq(o, current[i])) {
                        index = i;
                        break findIndex;
                    }
                }
                if (index >= len)
                    return false;
                if (current[index] == o)
                    break findIndex;
                index = indexOf(o, current, index, len);
                if (index < 0)
                    return false;
            }
            //复制一个数组
            Object[] newElements = new Object[len - 1];
            System.arraycopy(current, 0, newElements, 0, index);
            System.arraycopy(current, index + 1,
                             newElements, index,
                             len - index - 1);
            //替换原数组
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }


CopyOnWriteArraySet

CopyOnWriteArraySet 就是用 Array 实现的一个 Set,保证所有元素都不重复。其内部是封装的一个CopyOnWriteArrayList。

public class CopyOnWriteArraySet<E> extends AbstractSet<E> implements java.io.Serializable {
	// 新封装的CopyOnWriteArrayList
	private final CopyOnWriteArrayList<E> al;
	public CopyOnWriteArraySet() {
		al = new CopyOnWriteArrayList<E>();
	}
	public boolean add(E e) {
		return al.addIfAbsent(e); // 不重复的加进去
	}
}

总结

  • CopyOnWriterArrayList的源码还是比较简单的,重要的是以空间换时间的策略,这一点在并发编程的应用,如LongAdder,当线程不存在竞争的时候,首先将值写入到base中,当线程之间有竞争时内部维护了一个base值和一个cell数组。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

CopyOnWriterArrayList 的相关文章

  • Android Studio Gradle失败 Could not reolve play-services-vision-17.0.2.aar 等类似问题解决办法

    Android Studio Gradle失败 Could not reolve play services vision 17 0 2 aar 等类似问题解决办法 网上一些常规解决办法 如果上述常规解决办法尝试后 xff0c 都无法解决你
  • 利用fsl进行配准

    利用fsl进行配准 配准概念 配准就是将两个不同空间 体素 xff0c 扫描的位置不一致的nii xff0c 配准到同一个空间上 xff0c 使得两者在大脑上的相应位置就可以一一对应上了 通常MRI数据处理的步骤 xff1a 先配准到tem
  • 1.计算机概论

    学习linux前先来了解一下计算机概念 xff0c 如果了解相关内容 xff0c 可跳过本章节 1 1 电脑 电脑是一种计算机 xff0c 计算机实际是 xff1a 接受用户输入的命令与数据 xff0c 经由中央处理器的算术和逻辑单元运算后
  • SpringDataJPA——使用EntityManager利用原生SQL自定义复杂查询

    使用EntityManager 原生SQL查询方法记录以下学习过程中找到的其他文章地址 原生SQL查询方法 在这里进行记录以下使用过程 xff0c 注释已经很清晰 span class token annotation punctuatio
  • 操作系统(二十三)生产者消费者问题

    2 3 6 生产者消费者问题 生产者消费者问题 The proceducer consumer problem 是一个经典的进程同步的问题 xff0c 问题是这样描述的 xff1a 在操作系统中有一组生产者进程一组消费者进程 xff0c 生
  • Powershell脚本:一键优化windows 10(原版)

    本套Powershell脚本出自github开源项目 xff0c 包含原版WIN10系统大概300个一键优化 组件精简方案 例如彻底关闭Windows defender xff0c 关闭共享 打印机 xff0c 保留Windows upda
  • 安装ubuntu与windows双系统

    ubuntu程序的安装 开机进bios xff0c 在Security页面 xff0c 关掉secure boot xff1a 存储系统文件 xff0c 建议10GB 15GB xff1b swap xff1a 交换分区 xff0c 即Li
  • Windows编程经典书籍

    本人是刚刚开始学习windows编程的 感觉看雪学院的大牛很NB 想找一些书籍来看学习学习 可是不知道看哪些书好 驱动 对菜鸟们来说真是一个很深奥的话题 所以 我找来了这篇文章供大家分享 以后大家发现什么好书就在楼下跟贴吧 作者 xff1a
  • 经典Windows编程书单

    说好的这次写一个图形编程书单 但是看起来不是很好整理 xff0c 这类书散落的家里到处都是 先把经典Windows编程的书整理一下吧 xff0c 不过Windows的也到处都是很多都找不到了 xff0c 只能把找到的拍个照 xff0c 可能
  • ubuntu18.04开机循环输入密码无法进入桌面

    问题 xff1a 在profile和environment文件里配置了java环境变量后 xff0c 重启电脑后即使输入正确的用户名和密码 xff0c 也会重新跳到登录界面 xff0c 无法进入系统 xff0c 一直循环登录 原因 xff1
  • ubuntu 安装VS

    Table of Contents 一 前言 二 安装过程 1 下载VS Code 2 安装过程 3 下载C 43 43 模块 4 汉化 5 常用快捷键 一 前言 因为要用到在ubuntu系统中使用VS Code 来编写C 43 43 代码
  • Windows系统FTP服务器设置

    设置操作步骤 步骤一 xff1a 确认电脑是否开通联网共享服务 依次点击 控制面板 程序 启用或关闭Windows功能 按钮 xff0c 进入到 Windows功能 页面 xff0c 查看 Internet Information Serv
  • springboot thymeleaf 配置

    Springboot默认是不支持JSP的 xff0c 默认使用thymeleaf模板引擎 1 在application properties文件中增加Thymeleaf模板的配置 thymelea模板配置 spring thymeleaf
  • 【ubuntu】fatal: detected dubious ownership in repository at ...

    在ubuntu使用git的时候遇到了以下错误 xff1a fatal detected dubious ownership in repository at 39 home xxx 39 To add an exception for th
  • 有意思的Windows脚本(1)

    有意思的Windows脚本 1 因为不知道今天的博客写什么啦 xff0c 就放几个好玩的Windows脚本的源码吧 xff0c 大家千万不要干坏事情哦 xff0c 嘿嘿 1 vbs循环 xff08 桌面上建一个记事本 xff0c 输入下面代
  • 程序员3年5年10年三个阶段

    第一阶段 三年 三年对于程序员来说是第一个门槛 xff0c 这个阶段将会淘汰掉一批不适合写代码的人 这一阶段 xff0c 我们走出校园 xff0c 迈入社会 xff0c 成为一名程序员 xff0c 正式从书本上的内容迈向真正的企业级开发 我
  • 使用 matplotlib 轻松制作动画

    https www codenong com e264872efa062c7d6955 该链接讲了如何使用 matplotlib 轻松制作动画 xff0c 很好用
  • C#中使用IMemoryCache实现内存缓存

    1 缓存基础知识 缓存是实际工作中非常常用的一种提高性能的方法 缓存可以减少生成内容所需的工作 xff0c 从而显著提高应用程序的性能和可伸缩性 缓存最适用于不经常更改的数据 通过缓存 xff0c 可以比从原始数据源返回的数据的副本速度快得
  • 2021-09-13使用@Slf4j报错 程序包org.slf4j不存在

    导入两个maven依赖 然后就OK了 span class token tag span class token tag span class token punctuation lt span dependency span span c

随机推荐

  • PowerShell7.X的安装与美化

    参考链接1 xff1a https blog csdn net qq 39537898 article details 117411132参考链接2 xff1a https sspai com post 59380 很有参考价值 xff0c
  • Lab2 p3 围棋吃子的算法实现

    简单介绍下框架 xff1a 1 xff0e 声明一维数组block 作为一个临时变量记录一个块的大小 xff0c 声明一个整型blockLength记录这个块的长度 2 xff0e kill 为吃子的主函数 recersion int i
  • Python爬取皮皮虾视频

    背景 xff1a 今天闲着没事做 xff0c 然后想着刷刷视频 xff0c 然后发现前段时间学习了一下网络爬虫的一些基本应用 xff0c 就想着利用爬虫到网上去爬取一点视频来模拟人为的点击 下载操作 因为皮皮虾是手机端的app xff0c
  • 解决Result Maps collection already contains value for...BaseResultMap问题

    使用generatorSqlmapCustom逆向工程生成代码报错 假如使用generatorSqlmapCustom逆向工程生成代码 xff0c 即生成dao文件和mapper xml文件 xff0c 复制粘贴至工程中运行报错 Resul
  • IDEA2022.1的一些不常见问题解决方案

    文章目录 IDEA2022 1小问题解决方案 学习的时候尝鲜用了最新版本的IDEA 出现过以下老版本不会遇见的问题 Spring Initializer 创建的项目 无法新建module 显示Directory is already tak
  • 史上最全,Android P指纹应用及源码解析

    简单使用 源码分析 首先需要了解以下几点 指纹识别相关api是在Android23版本添加的 xff0c 所以过早版本的设备是无法使用的 xff1b android span class token punctuation span os
  • RNA-seq数据分析(HISAT2+featureCounts+StringTie)

    RNA seq数据分析 简介1 生物基础1 1 中心法则1 2 RNA seq Protocol1 3 RNA seq总的路线图 2 数据分析2 1 前期准备2 1 1 创建目录 amp 安装conda2 1 2 常用文件格式简介 2 2
  • Lottie动画的优劣及原理

    前言 Lottie是目前应用十分广泛的动画框架 在周会汇报的时候 xff0c 老板问能不能对Lottie进行优化 xff0c 于是就有了下文对Lottie原理的研究 毕竟要进行优化 xff0c 首先要深入了解原理嘛 Lottie实现 Lot
  • 详解微服务技术中进程间通信

    在单体应用中 xff0c 一个组件调用其它组组件时 xff0c 是通过语言级的方法或者函数调用 xff0c 而一个基于微服务的应用是运行于多个服务器上的分布式系统 xff0c 每个服务实例是一个典型的进程 所以 xff0c 如下图显示的 x
  • FusionCompute8.0.0实验(0)CNA及VRM安装(2280v2)

    给公司的华为泰山2280V2服务器安装CNA xff0c arm架构的 xff0c 采用方案为CNA和VRM在一个物理机上 准备文件 xff1a FusionCompute VRM 8 0 0 ARM 64 zip FusionComput
  • 网上买的st7789v3屏幕7脚的不能显示(1)

    今天通过网上购买了一款最便宜的1 3寸液晶显示屏分辨率240x240 xff0c 虽然小了一点 xff0c 但是看起来还不错 xff0c 于是准备了以前的用于驱动st7789的程序 xff0c 连接所有的引脚 xff0c 发现没有cs引脚
  • 新版idea中的terminal会打开windows的power shell窗口

    IDEA升级后发现点击terminal不会像之前一样显示在ide的底部而是会打开windows的Power Shell窗口 xff0c 此时需要找到windows Power Shell的位置右键属性在选项中 xff0c 取消勾选 使用旧版
  • 如何在非/home目录下下载安装vscode-server

    实现目标 xff1a 通过windows端的VSCODE xff0c 利用SSH工具在Ubuntu服务器的非 home目录下在下载安装vscode server 问题 xff1a 服务器 home文件夹剩余空间为0 xff0c 使用SSH工
  • Python 求解最大连通子网络问题

    记录一下不借助networkx包解决寻找最大连通子网络问题 这里没有源码 xff0c 只有问题解析 需要自己动手 这里是关键代码 xff1a span class token keyword for span i in span class
  • @Configuration的使用 和作用

    原文 从Spring3 0 xff0c 64 Configuration用于定义配置类 xff0c 可替换xml配置文件 xff0c 被注解的类内部包含有一个或多个被 64 Bean注解的方法 xff0c 这些方法将会被Annotation
  • @Component和@Configuration

    64 configuration和 64 component之间的区别是 xff1a 64 Component注解的范围最广 xff0c 所有类都可以注解 xff0c 但是 64 Configuration注解一般注解在这样的类上 xff1
  • zookeeper笔记

    ZooKeeper对分布式系统的协调 xff0c 使 共享存储解决分布式系统 临的问题 其实共享存储 xff0c 分布式应 也需要和存储进 络通信 大多数分布式系统中出现的问题 xff0c 都源于信息的共享出了问题 如果各个节点间信息不能及
  • Dubbo

    1 分布式架构 xff08 SOA 分层 按照业务性质分层 每一层要求简单 和 容易维护 应用层 距离用户最近的一层 也称之为接入层 使用tomcat 作为web容器 接收用户请求 使用下游的dubbo提供的接口来返回数据并且该层禁止访问数
  • Java的对象模型

    原文链接 对象在堆内存的布局分为三个区域 xff1a 分别是对象头 xff08 Header xff09 实例数据 xff08 Instance Data xff09 对齐填充 xff08 Padding xff09 对象头 xff1a 对
  • CopyOnWriterArrayList

    CopyOnWrite CopyOnWrite容器即写时复制的容器 通俗的理解是当我们往一个容器添加元素的时候 xff0c 不直接往当前容器添加 xff0c 而是先将当前容器进行Copy xff0c 复制出一个新的容器 xff0c 然后新的