当前位置: 首页 > news >正文

CopyOnWriteArrayList是如何保证线程安全的?

一:前言

在我们需要保证线程安全的时候,如果使用到Map,那么我们可以使用线程安全的ConcurrentHashMapConcurrentHashMap不仅可以保证线程安全,而且效率也非常不错,那有没有线程安全的List呢?答案是有,那就是CopyOnWriteArrayList。今天我们就一起来了解一下CopyOnWriteArrayList,看它是如何巧妙的保证线程安全的吧。

二:成员变量分析

    //进行修改操作时的锁
    final transient ReentrantLock lock = new ReentrantLock();

    //真正保存数据的数组 用volatile关键字进行修饰,保证array的引用的可见性
    private transient volatile Object[] array;
复制代码

三:源码分析

首先我们看构造方法,CopyOnWriteArrayList有三个构造方法。

1.空参构造

调用setArray方法将成员变量array赋值为一个长度为0的数组。

public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }
复制代码
final void setArray(Object[] a) {
        array = a;
    }
复制代码

2.传入一个Collection对象的构造方法

首先判断Collection是否是一个CopyOnWriteArrayList,如果是,直接将传入的CopyOnWriteArrayList的elements重新赋值给需要创建的CopyOnWriteArrayList。 如果不是,判断Collection是否是ArrayList,如果是,那么就利用toArray()方法将其转化为一个数组并赋值给成员变量array,否则将Collection里面的元素全部取出来copy到一个新数组中,并且将该数组赋值给成员变量array。

    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] elements;
        if (c.getClass() == CopyOnWriteArrayList.class)
            elements = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
            elements = c.toArray();
            if (c.getClass() != ArrayList.class)
                elements = Arrays.copyOf(elements, elements.length, Object[].class);
        }
        setArray(elements);
    }
复制代码

3.传入一个数组的构造方法

将传入的数组的元素copy到一个新的Object数组,并且赋值给成员变量array。

    public CopyOnWriteArrayList(E[] toCopyIn) {
        setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
    }
复制代码

接下来我们看核心的add(),remove(),get()方法。

add(E e)

首先加锁,然后通过Arrays.copyOf()方法将元素copy到一个新的数组中,新的数组的长度为原数组的长度+1,并且将需要加入的元素赋值到新数组的最后。最后将新数组赋值给成员变量array。

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();
        }
    }
复制代码

add(int index, E element)

add(int index, E element)方法需要将元素加入到指定的索引位置中。首先也是先加锁,保证线程安全,将原数组分为两段进行操作,根据index进行分隔,分别copy index之前的元素和之后的元素,copy完成之后在将需要插入的元素设置到索引为index的位置上。之后将新数组赋值给成员变量array。

public void add(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            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 {
                newElements = new Object[len + 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            newElements[index] = element;
            setArray(newElements);
        } finally {
            lock.unlock();
        }
    }
复制代码

接下来看remove()方法。

remove(int index)

remove(int index)方法需要在数组中移除指定索引的值。首先是加锁,同样也是将原数组分为两段进行操作,根据index进行分隔,分别copy index之前的元素和之后的元素,copy到一个新数组中,新数组的长度为原数组的长度减一(注意这里是没有copy index索引位置的值的,所以相当于移除了index索引上的值)。之后将新数组赋值给成员变量array。

    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)
                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();
        }
    }
复制代码

接下来看get()方法

get()

我们可以看到get()方法很简单,就是从array成员变量中取出对应索引的值。并没有加锁处理。所以尽管是在并发高的情况下,get()方法的效率依旧是比较高的。

    /**
     * {@inheritDoc}
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        return get(getArray(), index);
    }
    
    private E get(Object[] a, int index) {
        return (E) a[index];
    }
复制代码

四:总结

CopyOnWriteArrayList为什么能够保证线程安全,主要是因为以下几点:

1.在做修改操作的时候加锁

2.每次修改都是将元素copy到一个新的数组中,并且将数组赋值到成员变量array中。

3.利用volatile关键字修饰成员变量array,这样就可以保证array的引用的可见性,每次修改之前都能够拿到最新的array引用。这点很关键。

看到这里,相信你已经对CopyOnWriteArrayList非常了解了,CopyOnWriteArrayList在查询多,修改操作少的情况下效率是非常可观的,既能够保证线程安全,又能有不错的效率。但是如果修改操作较多,就会导致数组频繁的copy,效率就会有所下降,如果修改操作很多,那么直接使用Collections.synchronizedList(),或许也是一个不错的选择。

相关文章:

  • Synopsys新思科技2023“向新力”秋季校园招聘内推
  • 硬盘分区误删数据如何恢复呢?
  • 基于Java毕业设计智能超市导购系统源码+系统+mysql+lw文档+部署软件
  • python数据分析—删除value=0的行
  • 计算机毕业设计Java物流信息管理系统(源码+系统+mysql数据库+Lw文档)
  • [CSS]CSS 的背景
  • 大数据ClickHouse进阶(六):Distributed引擎深入了解
  • PyTorch、TensorFlow和Jax构建神经网络模型的标准化流程
  • 【最详细demo】雪花算法详细解释
  • 基于JavaSwing开发扫雷小游戏(不同版本) 课程设计 大作业
  • 【云原生 | Kubernetes 系列】---Ceph集群安装部署
  • 分组卷积/转置卷积/空洞卷积/反卷积/可变形卷积/深度可分离卷积/DW卷积/Ghost卷积/
  • java计算机专业招聘网站计算机毕业设计MyBatis+系统+LW文档+源码+调试部署
  • 如何用Python自动爬取全国30+城市地铁图数据?
  • 手把手教大家编译 flowable 源码
  • 时间复杂度分析经典问题——最大子序列和
  • @jsonView过滤属性
  • es6--symbol
  • extjs4学习之配置
  • HTTP中GET与POST的区别 99%的错误认识
  • Java 内存分配及垃圾回收机制初探
  • JavaScript 是如何工作的:WebRTC 和对等网络的机制!
  • spring + angular 实现导出excel
  • SpringBoot 实战 (三) | 配置文件详解
  • Spring技术内幕笔记(2):Spring MVC 与 Web
  • 扑朔迷离的属性和特性【彻底弄清】
  • 前端设计模式
  • 腾讯大梁:DevOps最后一棒,有效构建海量运营的持续反馈能力
  • 一道面试题引发的“血案”
  • 一文看透浏览器架构
  • 用Visual Studio开发以太坊智能合约
  • 云大使推广中的常见热门问题
  • #1014 : Trie树
  • #NOIP 2014# day.1 T2 联合权值
  • $(document).ready(function(){}), $().ready(function(){})和$(function(){})三者区别
  • $L^p$ 调和函数恒为零
  • ( 10 )MySQL中的外键
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (编译到47%失败)to be deleted
  • (附源码)springboot人体健康检测微信小程序 毕业设计 012142
  • (附源码)计算机毕业设计SSM基于健身房管理系统
  • (十二)devops持续集成开发——jenkins的全局工具配置之sonar qube环境安装及配置
  • (小白学Java)Java简介和基本配置
  • (正则)提取页面里的img标签
  • (转载)Linux 多线程条件变量同步
  • .net mvc actionresult 返回字符串_.NET架构师知识普及
  • .NET 使用配置文件
  • .NET 中创建支持集合初始化器的类型
  • .Net6使用WebSocket与前端进行通信
  • .Net中wcf服务生成及调用
  • /etc/sudoer文件配置简析
  • @GlobalLock注解作用与原理解析
  • [3300万人的聊天室] 作为产品的上游公司该如何?
  • [BJDCTF 2020]easy_md5
  • [BJDCTF2020]The mystery of ip