摘要:JDK中提供了CopyOnWriteArrayList类,简称COW。为了将读取的性能发挥到极致,CopyOnWriteArrayList读取是完全不用加锁的,并且更厉害的是:写入也不会阻塞读取操作。

本文分享自华为云社区《面试官:如何安全地使用List》,作者:李哥技术。

今天我们来讨论一个JUC中的集合类CopyOnWriteArrayList。

为什么研究这个类

在很多应用场景中,对于集合的读操作的频率一定会远远大于写操作。由于读操作根本不会修改原有的数据,因此对于每次读取都进行加锁其实是一种资源浪费。我们应该允许多个线程同时访问List的内部数据,毕竟读取操作是线程安全的。

JDK中提供了CopyOnWriteArrayList类,简称COW。为了将读取的性能发挥到极致,CopyOnWriteArrayList读取是完全不用加锁的,并且更厉害的是:写入也不会阻塞读取操作。只有写入和写入之间需要进行同步等待。这样一来,读操作的性能就会大幅度提升。那它是怎么做的呢?来吧,让我们一起研究一下。

设计原理

CopyOnWriteArrayList底层实现是通过Object[]存储元素的,内部的可变操作(add,set 等方法)都是把数据copy到一个新数组里,对新数组进行操作,再把新数组赋值给原来的对象,从而达到修改目的。

这样做的好处是不修改原数组,所以写操作不会影响到读操作。

从 CopyOnWriteArrayList 的名字就能看出CopyOnWriteArrayList 是满足CopyOnWrite 的 ArrayList,所谓CopyOnWrite 也就是说:在计算机,如果你想要对一块内存进行修改时,我们不在原有内存块中进行写操作,而是将内存拷贝一份,在新的内存中进行写操作,写完之后呢,就将指向原来内存指针指向新的内存,原来的内存就可以被回收掉了。

定位

public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
}

从类的继承关系来看

  1. 实现RandomAccess接口,说明可随机访问
  2. 实现Cloneable接口,说明可克隆
  3. 实现了List接口,说明是一个列表
  4. 实现Serializable接口,说明可序列化

接下来让我们研究一下crud。

public boolean add(E e);// 新增元素,放在数组尾部
public void add(int index, E element);// 新增元素,放在数组指定位置
public boolean addIfAbsent(E e);// 新增元素,如果存在则返回false,如果不存在则放入末尾返回true
public int addAllAbsent(Collection<? extends E> c);// 批量新增元素,将指定集合中尚未包含在此列表中的所有元素附加到此列表的末尾,返回添加的个数
public boolean addAll(Collection<? extends E> c);// 将指定集合中的所有元素附加到此列表的末尾。
public boolean addAll(int index, Collection<? extends E> c);// 从指定位置开始,将当前位于该位置的元素(如果有)和任何后续元素向右移动(增加它们的索引)。新元素将按照指定集合的迭代器返回的顺序出现在此列表中。

此函数用于将指定元素添加到此列表的尾部,处理流程如下:

  • 获取锁(保证线程安全)
  • 根据Object数组复制一个长度为length+1的Object数组为newElements(此时,newElements[length]为null)
  • 将下标为length的数组元素newElements[length]设置为元素e,再设置当前Object[]为newElements,释放锁,返回。这样就完成了元素的添加。

public E remove(int index);// 移除指定位置的元素,有可能抛出数组越界异常
public boolean remove(Object o);// 移除对象,如果不存在则返回false,存在则移除后返回true
public boolean removeAll(Collection<?> c);// 批量移除指定集合元素,这是一个非常消耗内存的方法,因为内部会额外指定一个临时数组用来存放需要保留的元素,一共涉及4个数组(老数组、传入数组、临时数组、结果数组)
public boolean removeIf(Predicate<? super E> filter);// 和removeAll类似,内部实现需要有临时数组,也是代价昂贵的方法,请谨慎使用

指定位置删除的逻辑如下:

  • 获取锁
  • 获取数组,数组长度
  • 获取指定位置的元素(可能抛出数组越界异常)
  • 计算指定位置的元素是否是当前数组的最后一个
  • 如果是最后一个->不需要挪数据,只需要创建数组,copy数据到数组即可(少copy最后一个),设置数组并返回即可
  • 如果不是最后一个->创建数组,copy 0~index的数据到新数组,再copy (index+1)的数据到新数组,设置数组并返回即可

set方法,修改操作有可能数组越界,这一点需要注意。修改操作也是基于copy的,将数据copy到新数组,对新数组进行替换后再设置数组,从而达到set的目的。

public E get(int index);// 直接从数组中获取,可能抛出数组越界异常
public Spliterator<E> spliterator();
public Iterator<E> iterator();// 获取数组的迭代器,它的实现类是COWIterator,内部拥有一个快照的数组属性
public ListIterator<E> listIterator();// 获取listIterator迭代器,它的实现类是COWIterator,内部拥有一个快照的数组属性
public ListIterator<E> listIterator(int index);// 获取listIterator迭代器,index的作用是设置迭代器当前迭代的位置

先来看一个内部类COWIterator:

COWIterator表示一个迭代器,其也有一个Object类型的数组作为CopyOnWriteArrayList数组的快照,这种快照风格的迭代器方法在创建迭代器时使用了对当时数组状态的引用。此数组在迭代器的生存期内不会更改,因此不可能发生冲突,并且迭代器保证不会抛出 ConcurrentModificationException。在创建迭代器以后,迭代器就不会反映列表的添加、移除或者更改,因为在迭代器上进行的元素更改操作(remove、set 和 add)不受支持。这些方法将抛出 UnsupportedOperationException。

更深入的理解

CopyOnWriteArrayList每次写操作都会申请新内存空间,如果数据量较大的话,很容易触发young gc或者full gc,并且拷贝也会比较消耗内存,虽然适合读多写少的应用场景,在互联网应用中,数据量稍微有点多再操作add或set,非常容易引起故障,还是要谨慎使用。

再谈读,迭代读的时候是读取快照数据,只要生成了迭代器,迭代内的快照内容将保证不会发生改变,所以不适合用于实时读场景。

点击关注,第一时间了解华为云新鲜技术~

关于Copy On Write Array List,你会安全使用么的更多相关文章

  1. Swift Array copy 的线程安全问题

    Swift Array copy 的线程安全问题 NSArray 继承自 NSObject,属于对象,有 copy 方法.Swift 的 Array 是 struct,没有 copy 方法.把一个 A ...

  2. 探究@property申明对象属性时copy与strong的区别

    一.问题来源 一直没有搞清楚NSString.NSArray.NSDictionary--属性描述关键字copy和strong的区别,看别人的项目中属性定义有的用copy,有的用strong.自己在开 ...

  3. Array类

    class Array Arrays are ordered, integer-indexed collections of any object. Array indexing starts at ...

  4. assign、copy 、retain等关键字的含义

    assign: 简单赋值,不更改索引计数copy: 建立一个索引计数为1的对象,然后释放旧对象retain:释放旧的对象,将旧对象的值赋予输入对象,再提高输入对象的索引计数为1Copy其实是建立了一个 ...

  5. IOS 关键字self,super,copy, retain, assign , readonly , readwrite, nonatomic、 @synthesize、@property、@dynamic

    IOS 关键字self,super,copy, retain, assign , readonly , readwrite, nonatomic.                     @synth ...

  6. IOS开发 strong,weak,retain,assign,copy nomatic 等的区别与作用

    strong,weak,retain,assign,copy nomatic 等的区别 copy与retain:1.copy其实是建立了一个相同的对象,而retain不是:2.copy是内容拷贝,re ...

  7. iOS - property,strong,weak,retain,assign,copy,nomatic 的区别及使用

    1:ARC环境下,strong代替retain.weak代替assign,xcode 4.2(ios sdk4.3和以下版本)和之前的版本使用的是retain和assign,是不支持ARC的.xcod ...

  8. iOS - OC Copy 拷贝

    前言 copy:需要先实现 NSCopying 协议,创建的是不可变副本. mutableCopy:需要实现 NSMutableCopying 协议,创建的是可变副本. 浅拷贝:指针拷贝,源对象和副本 ...

  9. C++/C#中堆栈、对象内存模型、深浅拷贝、Array.Clone方法

    转载自:http://blog.csdn.net/jarvischu/article/details/6425534 目录 1.      C++/C#中对象内存模型................. ...

随机推荐

  1. pandas:多层索引

    多层索引是指在行或者列轴上有两个及以上级别的索引,一般表示一个数据的几个分项. 1.创建多层索引 1.1通过分组产生多层索引 1.2由序列创建 1.3由元组创建 1.4可迭代对象的笛卡尔积 1.5将D ...

  2. NOI Online 2022 一游

    NOI Online 2022 一游 TG 啊,上午比提高,根据去年的经验,题目配置估计那至少一黑 所以直接做 1 题即可.(确信) 总体:估分 140,炸了但没完全炸 奇怪的过程 开题:3 2 1 ...

  3. vue传值的几种方式

    props:适用于 父组件 ==> 子组件 通信 由父组件传值子组件在props中接收即可: (由父组件给子组件传递 函数类型 的props可实现 子组件 ==> 父组件 传递数据,较为繁 ...

  4. Node.js精进(1)——模块化

    模块化是一种将软件功能抽离成独立.可交互的软件设计技术,能促进大型应用程序和系统的构建. Node.js内置了两种模块系统,分别是默认的CommonJS模块和浏览器所支持的ECMAScript模块. ...

  5. 【2022-06-16】Pycharm的下载与安装使用

    一.Pycharm软件简介 产品简介 PyCharm是一种Python IDE(Integrated Development Environment,集成开发环境),带有一整套可以帮助用户在使用Pyt ...

  6. 2022年最强八股文《码出八股文-斩出offer线》

    宝剑锋从磨砺出,梅花香自苦寒来,大家好,我是小码哥 整理好的八股文终于完成了,希望看完对大家面试有所收获! 目录: 基础篇 javaOOP面试题 java集合/泛型面试题 java异常面试题 java ...

  7. Canal搭建

    目录 搭建环境 MySQL环境 Canal环境 客户端 总结 需求:解决私有环境数据库的基础数据同步问题,每当中心库基础数据发生改变时,其他私有库都会增量同步 Canal主要用途是基于 MySQL 数 ...

  8. NC212914 牛牛与后缀表达式

    NC212914 牛牛与后缀表达式 题目 题目描述 给定牛牛一个后缀表达式 \(s\) ,计算它的结果,例如,1+1对应的后缀表达式为1#1#+,'#'作为操作数的结束符号. 其中,表达式中只含有'+ ...

  9. Tomcat深入浅出——Servlet(二)

    一.Servlet简介 Servlet类最终开发步骤: 第一步:编写一个Servlet类,直接继承HttpServlet 第二步:重写doGet方法或者doPost方法,重写哪个我说的算! 第三步:将 ...

  10. 安装rlwrap

    一. 安装readlineyum install readline* -y 二. 安装rlwrap[root@dbserver ~]# tar -zxvf rlwrap-0.43.tar.gz[roo ...