首页 > 编程语言 >java并发数据结构之CopyOnWriteArrayList

java并发数据结构之CopyOnWriteArrayList

时间:2022-12-05 16:35:55浏览次数:45  
标签:index elements java int lock Object len CopyOnWriteArrayList 数据结构

CopyOnWriteArrayList是一个线程安全的List实现,其在对对象进行读操作时,由于对象没有发生改变,因此不需要加锁,反之在对象进行增删等修改操作时,它会先复制一个对象副本,然后对副本进行修改,最后将修改后的副本对象写回,从而保证操作的线程安全,下面我们看一下具体的代码实现。

###构造函数

通过CopyOnWriteArrayList链表的构造,可以看出主要是依赖ReentrantLock与数组实现线程安全的链表

/** The lock protecting all mutators */
    final transient ReentrantLock lock = new ReentrantLock();

    /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;

     /**
     * Creates an empty list.
     */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

写操作

add实现

add是一个标准的使用ReentrantLock加锁保证线程安全操作的实现

    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#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);//copy一个副本对象
            newElements[len] = e;//赋值
            setArray(newElements);//把对象写回去
            return true;
        } finally {
            lock.unlock();//释放锁
        }
    }

    /**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    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的实现中我们可以看到在实际执行操作之前,会对对象的线程安全进行再次检查,另外在执行定位下标操作时基于原有下标进行分段定位的优化,一定概率上会降低循环复杂度

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();
        }
    }

    public boolean remove(Object o) {
        Object[] snapshot = getArray();
        int index = indexOf(o, snapshot, 0, snapshot.length);//遍历数组定位元素下标
        return (index < 0) ? false : remove(o, snapshot, index);
    }

    /**
     * A version of remove(Object) using the strong hint that given
     * recent snapshot contains o at the given 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)//下标超过当前数组长度返回false
                    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];//创建一个长度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();
        }
    }

读操作

读操作非常简单,无需加锁

/**
     * {@inheritDoc}
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E get(int index) {
        return get(getArray(), index);
    }

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

      通过对源码的分析,可以看到CopyOnWriteArrayList只在需要保证线程安全的写操作上加锁,核心思想就是减少锁竞争,从而提高并发时的读取性能,适用于写少读多的应用场景。

      以上就是对CopyOnWriteArrayList内部核心源码的基本走读与解析,其线程安全的实现模式很有代表意义,十分值得初学者参考与学习,希望对大家能有所帮助,其中如有不足与不正确的地方还望指正与海涵,十分感谢。

 

关注微信公众号,查看更多技术文章。

标签:index,elements,java,int,lock,Object,len,CopyOnWriteArrayList,数据结构
From: https://www.cnblogs.com/dafanjoy/p/16950488.html

相关文章

  • jdk自带的javaVisualVM检测tomcat
    背景:在项目运行的过程中想了解一下tomcat的执行性能情况,下面以jdk自带的javaVisualVm为例进行配置检测1.在我本地(windows系统)找到jdk中的bin目录,找到jvisualvm.exe双击......
  • javaScript概述
    目录JS简介JS基础变量与常量基本数据类型运算符流程控制函数内置对象JS简介全称JavaScript但是与Java一毛钱关系都没有之所以这么叫是为了蹭Java的热度它是一门前端工......
  • Java 编程入门第一课:HelloWorld
    在之前的文章中,壹哥带大家搭建出了Java的开发环境,配置了JDK环境变量,并且我们也熟悉了dos命令行的操作。那么从这篇文章开始,壹哥就开始带各位真正地学习Java代码该......
  • 力扣1337(java&python)-矩阵中战斗力最弱的 K 行(简单)
    题目:给你一个大小为 m *n 的矩阵 mat,矩阵由若干军人和平民组成,分别用1和0表示。请你返回矩阵中战斗力最弱的 k 行的索引,按从最弱到最强排序。如果第 i 行......
  • JavaDoc生成文档
    JavaDoc生成文档一.通过命令行生成JavaDoc文档1.打开指定的文件目录选中指定文件(类或者包)--->右键选中openin---->explorer2.打开指定文件的cmd再1中打开的文......
  • redis底层数据结构总结
    hash:是一维数组加链表 ziplink:压缩列表相当于数组,链表查询速度快,查找慢跳表:是个有序的链表,实现有序数组的二分查找,缺点是占用更多的内存空间。跳表是每隔2个元素选出一......
  • JavaScript深浅拷贝
    基本类型&引用类型ECMAScript中的数据类型可分为两种:基本类型:undefined,null,Boolean,String,Number,Symbol引用类型:Object,Array,Date,Function,RegExp等不同类......
  • java面试
    目录枚举泛型用反射动态给对象的某个属性赋值导包带*有什么影响idea如何取消自动导包为*BIO和NIO和AIO枚举点击查看代码packagecom.cdjdgm.pdms.enums;/***供电......
  • 如何通过Java将Word转换为PDF
    Word是我们日常编辑文档内容时十分常用的一种文档格式。但相比之下,PDF文档的格式、布局更为固定,不易被更改。在保存或传输较为重要的文档内容时,PDF文档格式也时很多人的不......
  • Java 8 stream 合并map 分组计算
    Map<String,Map<String,Long>>map=newHashMap<>();Map<String,Long>param1=newHashMap<>();param1.put("a",100L);param1.put("b",200L);param1.put("c......