并发集合(二):CopyOnWriteArrayList

news2024/11/15 2:20:20

1、CopyOnWriteArrayList介绍

      CopyOnWriteArrayList 是一个线程安全的ArrayList。
      CopyOnWriteArrayList 是基于Lock锁和线程副本的形式来保证线程安全的,
      在写数据时,先获取Lock锁,然后复制一个副本,添加数据时,是往副本数组中添加数据,最        后再将副本数组赋值给自身array属性,这样可以保证读写分离(读写完全独立),从而提高          读写性能
      CopyOnWriteArrayList 是弱一致性的,写操作先操作,但是副本还没落CopyOnWriteArrayList        的 array属性中,此时读操作是无法读取刚写入的数据的。 

       注意:
              CopyOnWriteArrayList 每次写操作都要复制一个副本,若业务场景是写多多少,并且数                  据量比较大时,则尽量避免使用 CopyOnWriteArrayList;因为这里会创建大量的数组副                  本,比较占用内存资源;数组数据量很大时,数组的复制也是很消耗内存资源的

2、核心属性&方法

      CopyOnWriteArrayList 比较简单,只看下2个属性,2个设置array属性的方法及1个无参构造

      函数就行了,如下所示:

//lock锁,写操作时先获取锁
final transient ReentrantLock lock = new ReentrantLock();
//真正保存数据的数组
private transient volatile Object[] array;

/**
     * 获取array属性
     */
    final Object[] getArray() {
        return array;
    }

    /**
     * 替换array属性
     */
    final void setArray(Object[] a) {
        array = a;
    }

    public CopyOnWriteArrayList() {
        //默认数组长度为0
        //因为每次写数据时,都会构建一个全新的数组,所以在 CopyOnWriteArrayList 中也没有数组扩容的操作
        setArray(new Object[0]);
    }

3、读操作

     CopyOnWriteArrayList 读操作就是执行get方法,通过数组的索引位置来获取数据;

     具体方法如下:

            

4、写操作

     CopyOnWriteArrayList 写操作是基于Lock 锁+数组副本来保证写操作的线程安全的;

      且与读操作隔离,相互独立,互不影响。

      写操作方法如下:

      4.1)add(E e) 方法

               默认在副本数组的末尾添加数据

   /**
     * 添加数据,不指定索引位置,默认是在最后边添加数据
     */
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        //获取锁
        lock.lock();
        try {
            //构建array属性的副本
            Object[] elements = getArray();
            int len = elements.length;
            //创建新的数组,并将副本elements 的数据赋值到 newElements 中
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //添加数据,在最后添加
            newElements[len] = e;
            //将 newElements 赋值给array属性
            setArray(newElements);
            return true;
        } finally {
            //释放锁
            lock.unlock();
        }
    }

      4.2)add(E e,int index) 方法

               在指定索引位置index处添加数组,但不会覆盖索引index原有的数据

   /**
     * 在指定位置index处添加数据,但不会覆盖数据
     */
    public void add(int index, E element) {
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lock();
        try {
            //获取array属性的值,即复制 array 副本
            Object[] elements = getArray();
            int len = elements.length;
            //判断索引位置是否合法 0<= index <= len
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
            Object[] newElements;
            int numMoved = len - index;
            if (numMoved == 0) //表示新数据需要放到当前数组的最后边
                newElements = Arrays.copyOf(elements, len + 1);
            else {//表示当前数据需要放到索引在[0,len-1] 的范围内
                //创建新的数组,长度是array属性长度加1
                newElements = new Object[len + 1];
                //将老数组elements的[0,index) 位置的数据复制到新数组 newElements 的[0,index)处
                System.arraycopy(elements, 0, newElements, 0, index);
                //将老数组elements的[index,len) 位置的数据复制到新数组 newElements 的[index+1,len]处
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            //数据正常放到索引位置index处
            newElements[index] = element;
            setArray(newElements);
        } finally {
            //释放锁
            lock.unlock();
        }
    }

5、移除数据

     CopyOnWriteArrayList 移除数据有2种情况,一是根据索引index删除,二是删除指定数据,若

     数据不存在,则返回null

     5.1、remove(int index):据索引index删除指定位置的数据

              

public E remove(int index) {
        final ReentrantLock lock = this.lock;
        //加锁
        lock.lock();
        try {
            //获取array属性值
            Object[] elements = getArray();
            int len = elements.length;
            //获取指定索引位置index处的值
            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];
                //删除数据
                //将旧数组 elements 的[0,index)处的数据复制到新数组 newElements 的[0,index)处
                System.arraycopy(elements, 0, newElements, 0, index);
                //将旧数组 elements 的[index+1,len)处的数据复制到新数组 newElements 的[index,len-1)处
                System.arraycopy(elements, index + 1, newElements, index,
                                 numMoved);
                //设置array属性
                setArray(newElements);
            }
            //返回删除的数据
            return oldValue;
        } finally {
            lock.unlock();
        }
    }

     5.2、remove(Object o):删除指定的数据

public boolean remove(Object o) {
        //获取array的副本
        Object[] snapshot = getArray();
        //获取要删除数据的位置索引,若数据不存在,则返回-1
        //todo 注意:若是并发场景下,这里获取到的数据o的位置index不一定正确
        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 {
            //新获取array的副本
            Object[] current = getArray();
            int len = current.length;
            // findIndex 是if 代码块的名称
            // snapshot != current 表示并发下array已经被其他线程修改了,
            // 那么参数传递来的删除数据o的位置index也不正确,需要重新获取index
            if (snapshot != current) findIndex: {
                //若 index>len :则说明array被其他线程删除了index之后的数据
                //index<= len: 则表示array可能新增、删除了数据
                int prefix = Math.min(index, len);
                for (int i = 0; i < prefix; i++) {
                    //current[i] != snapshot[i]:表示数组array在并发下发生了改变
                    //eq(o, current[i]):判断当前i位置的数据与要删除的数据o是否一致,若一致,则将i赋值给index
                    if (current[i] != snapshot[i] && eq(o, current[i])) {
                        index = i;
                        //退出if块
                        break findIndex;
                    }
                }
                //如果for循环结束还没找到要删除的数据,则表示数组array发生了改变
                //删除数据不存在
                if (index >= len)
                    return false;
                //判断当前index位置的数据是否是要删除的数据,若是,则跳出if 代码块 findIndex
                if (current[index] == o)
                    break findIndex;
                //重新查找删除数据o的索引位置
                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);
            //设置array属性
            setArray(newElements);
            return true;
        } finally {
            //释放锁
            lock.unlock();
        }
    }

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

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

相关文章

Delphi7实现Json对象的序列化与反序列化

在高版本的 Delphi 中&#xff0c;实现序列化和反序列化非常简单。然而&#xff0c;在 Delphi 7 中&#xff0c;这个过程仍然需要一些额外的努力。为了简化这个问题&#xff0c;我花了一些时间封装了一个支持序列化和反序列化的 JSON 解析库。 type{$M}TStartupParameters cla…

MySQL的服务器与客户端:架构解析与实践

文章目录 MySQL的服务器和客户端服务端处理客户端请求连接管理解析与优化查询缓存语法解析查询优化 存储引擎不同的存储引擎查看支持的存储引擎为不同的表设置存储引擎 MySQL是一个广泛使用的开源关系数据库管理系统&#xff0c;其核心架构由服务器端和客户端两大部分组成。本文…

9/3 链表-力扣160 、203、206

160.相交链表 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个链式结构中不存在环。 注意&#xff0c;函…

CUDA-MODE课程笔记 第9课: 归约(也对应PMPP的第10章)

我的课程笔记&#xff0c;欢迎关注&#xff1a;https://github.com/BBuf/how-to-optim-algorithm-in-cuda/tree/master/cuda-mode CUDA-MODE课程笔记 第9课: 归约&#xff08;也对应PMPP的第10章&#xff09; 课程笔记 本节课的题目。 这节课的内容主要是 Chapter 10 of PMPP …

TCP连接重置,到底怎么回事?还是得网工大佬来分析!

来源&#xff1a;科来。 连接建立失败并不仅仅包含无响应问题&#xff0c;还有一种常见的情况&#xff0c;即RST&#xff08;Reset&#xff09;包的发送。RST包是TCP协议中用来进行“连接重置”的数据包&#xff0c;本文将围绕RST包进行详细展开讨论。 TCP连接中为何会有RST包…

VideoCrafter1:Open Diffusion models for high-quality video generation

https://zhuanlan.zhihu.com/p/677918122https://zhuanlan.zhihu.com/p/677918122 视频生成无论是文生视频,还是图生视频,图生视频这块普遍的操作还是将图片作为一个模态crossattention进unet进行去噪,这一步是需要训练的,svd除此之外,还将图片和noise做拼接,这一步,很…

【压测】ab命令

安装 sudo yum install httpd-toolssudo apt update sudo apt install apache2-utils介绍 ab&#xff1a;参数数量错误 用法&#xff1a;ab [选项] [http[s]://]主机名[:端口]/路径 选项包括&#xff1a; -n 请求次数 执行的请求数 -c 并发数 同时发起的多个请求数量 -t 时间…

springboot博客系统

基于springbootvue实现的博客系统 &#xff08;源码L文ppt&#xff09;4-031 4 系统设计 博客系统的整体结构设计主要分为两大部分&#xff1a;管理员和博主。他们的权限不同&#xff0c;于是操作功能也有所不同。整体结构设计如图4-2所示。 图4-2 系统结构图 4.3 数据库设…

HALCON与LabVIEW的联合编程 视觉与控制结合

HALCON与LabVIEW的联合编程在工业自动化和视觉检测领域中越来越受到重视。通过将HALCON的强大图像处理能力与LabVIEW的灵活控制功能相结合&#xff0c;工程师们可以开发出高效且精确的自动化系统。这种整合不仅提高了系统的整体性能&#xff0c;还简化了开发流程。本文将详细介…

前端DatePicker组件设置默认日期并限制可选日期范围

前言 在前端 element-ui 组件库中有一款组件叫做 DatePicker&#xff0c;是一个灵活选择日期的封装组件&#xff0c;它既能选择单个日期&#xff0c;也能选择一个日期范围&#xff08;两个日期的组合&#xff09;&#xff0c;后者的应用场景主要有以下两类&#xff1a;1、作为…

妙用市场情绪找出大盘买卖点,逆向交易5年3倍|邢不行

这是邢不行第 118 期量化小讲堂的分享 作者 | 邢不行、密斯锌硒 前言&#xff1a;有这么一个交易品种&#xff0c;它时而是身披圣光的天使&#xff0c;让人一夜间财富暴涨&#xff0c;时而又化身诱人疯狂的恶魔&#xff0c;让人一息间血本无归&#xff0c;我们似乎很了解它&a…

基于plc的变压器冷却系统设计(论文+源码)

1总体方案设计 通过需求分析&#xff0c;本设计基于PLC的变压器冷却系统的整体结构如图2.1所示&#xff0c;系统采用S7-200 PLC为控制器&#xff0c;其结合温度传感器、电压电流传感器、主风机、备用风机等构成整个系统&#xff0c;具有手动和自动两种模式&#xff0c;在手动模…

C++——入门基础(下)

目录 一、引用 &#xff08;1&#xff09;引用的概念和定义 &#xff08;2&#xff09;引用的特性 &#xff08;3&#xff09;引用的使用 &#xff08;4&#xff09;const引用 &#xff08;5&#xff09;指针和引用的关系 二、inline 三、nullptr 四、写在最后 一、引用…

4.1 数据分析-excel 基本操作

第四节&#xff1a;数据分析-excel 基本操作 课程目标 学会excel 基本操作 课程内容 数据伪造 产生一份招聘数据 import pandas as pd from faker import Faker import random import numpy as np# 创建一个Faker实例&#xff0c;用于生成假数据&#xff0c;指定中文本地…

【Steam游戏星露谷物语添加Mod步骤】

Steam游戏星露谷物语添加Mod步骤 星露谷物语添加拖拉机模组一、安装SMAPI二、正式开始添加MOD 星露谷物语添加拖拉机模组 一、安装SMAPI 星露谷物语添加拖拉机mod为例&#xff0c;添加其它mod一样的步骤。 首先&#xff0c;打开Steam&#xff0c;打开一次星露谷物语这款游戏&…

echarts遍历区域折线图,单线和多线

// 单线折线图drawonelineCharts(){var echarts require("echarts");var lineCharts document.getElementsByClassName(lineChart); // 对应地使用ByClassNamethis.linecolor[#01FFD4,#1C70DD,#01FFD4,#1C70DD,#01FFD4,#1C70DD]for(var i 0;i < lineCharts.len…

2024.9.2

还没写完 #include <iostream> #include <cstring> using namespace std;class myString { private:char *str; //字符串int size; //实际字符长度int len; //字符串容量 public:myString():size(10) //无参构造函数{len siz…

大模型入门 ch01:大模型概述

本文是github上的大模型教程LLMs-from-scratch的学习笔记&#xff0c;教程地址&#xff1a;教程链接 STAGE 1&#xff1a; BUILDING 1. 数据准备与采样 LLM的预测过程&#xff0c;是一个不断预测下一个词&#xff08;准确的说是token&#xff09;的过程&#xff0c;每次根据输…

太狠了:华为的卫星通信能免费使用了

在科技日新月异的今天&#xff0c;手机已不再仅仅是通讯工具&#xff0c;它们正逐步成为我们生活中的全能助手。 而华为&#xff0c;作为科技领域的佼佼者&#xff0c;再次以其创新技术引领了一场卫星通信的革命。 近日&#xff0c;华为正式官宣Pura 70系列首发支持中国移动北…

压缩PDF,介绍这五种压缩方案

压缩PDF&#xff0c;在现代信息社会中&#xff0c;PDF文件已经成为我们日常工作和学习中不可或缺的重要载体。但随着PDF文件内容的增多和复杂化&#xff0c;文件大小的膨胀也成为一个常见问题&#xff0c;给存储、共享和传输带来了不少挑战。今天&#xff0c;我们将详细介绍五种…