关于Copy On Write Array List,你会安全使用么
摘要: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 {
}

从类的继承关系来看
- 实现RandomAccess接口,说明可随机访问
- 实现Cloneable接口,说明可克隆
- 实现了List接口,说明是一个列表
- 实现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,你会安全使用么的更多相关文章
- Swift Array copy 的线程安全问题
Swift Array copy 的线程安全问题 NSArray 继承自 NSObject,属于对象,有 copy 方法.Swift 的 Array 是 struct,没有 copy 方法.把一个 A ...
- 探究@property申明对象属性时copy与strong的区别
一.问题来源 一直没有搞清楚NSString.NSArray.NSDictionary--属性描述关键字copy和strong的区别,看别人的项目中属性定义有的用copy,有的用strong.自己在开 ...
- Array类
class Array Arrays are ordered, integer-indexed collections of any object. Array indexing starts at ...
- assign、copy 、retain等关键字的含义
assign: 简单赋值,不更改索引计数copy: 建立一个索引计数为1的对象,然后释放旧对象retain:释放旧的对象,将旧对象的值赋予输入对象,再提高输入对象的索引计数为1Copy其实是建立了一个 ...
- IOS 关键字self,super,copy, retain, assign , readonly , readwrite, nonatomic、 @synthesize、@property、@dynamic
IOS 关键字self,super,copy, retain, assign , readonly , readwrite, nonatomic. @synth ...
- IOS开发 strong,weak,retain,assign,copy nomatic 等的区别与作用
strong,weak,retain,assign,copy nomatic 等的区别 copy与retain:1.copy其实是建立了一个相同的对象,而retain不是:2.copy是内容拷贝,re ...
- iOS - property,strong,weak,retain,assign,copy,nomatic 的区别及使用
1:ARC环境下,strong代替retain.weak代替assign,xcode 4.2(ios sdk4.3和以下版本)和之前的版本使用的是retain和assign,是不支持ARC的.xcod ...
- iOS - OC Copy 拷贝
前言 copy:需要先实现 NSCopying 协议,创建的是不可变副本. mutableCopy:需要实现 NSMutableCopying 协议,创建的是可变副本. 浅拷贝:指针拷贝,源对象和副本 ...
- C++/C#中堆栈、对象内存模型、深浅拷贝、Array.Clone方法
转载自:http://blog.csdn.net/jarvischu/article/details/6425534 目录 1. C++/C#中对象内存模型................. ...
随机推荐
- Python列表推导式,字典推导式,元组推导式
参考:https://blog.csdn.net/A_Tu_daddy/article/details/105051821 my_list = [ [[1, 2, 3], [4, 5, 6]] ] f ...
- JavaScript 防盗链的原理以及破解方法
先说说防盗链的原理,http 协议中,如果从一个网页跳到另一个网页,http 头字段里面会带个 Referer.这里的Referer是由于历史原因导致了拼写错误 后来也就一直沿用.图片服务器通过检测 ...
- 8. SparkSQL综合作业
综合练习:学生课程分数 网盘下载sc.txt文件,分别创建RDD.DataFrame和临时表/视图: 分别用RDD操作.DataFrame操作和spark.sql执行SQL语句实现以下数据分析: 总共 ...
- 爬虫(14) - Scrapy-Redis分布式爬虫(1) | 详解
1.什么是Scrapy-Redis Scrapy-Redis是scrapy框架基于redis的分布式组件,是scrapy的扩展:分布式爬虫将多台主机组合起来,共同完成一个爬取任务,快速高效地提高爬取效 ...
- 什么是pytorch?
Pytorch是基于python的科学计算包,为两类受众提供服务 作为Numpy的替换,让你可以使用GPU的算力 作为一个深度学习计算平台提供最大的计算灵活性与速度 开始体验pytorch的基础功能 ...
- DTCC 干货分享:Real Time DaaS - 面向TP+AP业务的数据平台架构
2021年10月20日,Tapdata 创始人唐建法(TJ)受邀出席 DTCC 2021(中国数据库技术大会),并在企业数据中台设计与实践专场上,发表主旨演讲"Real Time Daa ...
- Mysql数据库的默认引擎
InnoDB的优势在于提供了良好的事务处理.崩溃修复能力和并发控制.缺点是读写效率较差,占用的数据空间相对较大. ①InnoDB:支持事务处理,支持外键,支持崩溃修复能力和并发控制.如果需要对事务的完 ...
- Java编程作业
1.编程题 设计一个用户类User,类中的变量有用户名.密码和记录用户数量的变量,定义3个构造方法:无参的.为用户名赋值的.为用户名和密码赋值的,还有获取和设置密码的方法和返回类信息的方法. pack ...
- 4-7 CS后台项目练习-1
1. 关于此项目 此项目是一个自营性质电商类型的项目. 当前目标是设计后台管理相关功能. 2. 关于项目的开发流程 开发项目的标准流程应该有:需求分析.可行性分析.总体设计.详细设计等. 建议课后学习 ...
- 二叉排序树的合并(严3.98)--------西工大noj
二叉排序树的合并有三种方法 先存入数组,然后..... 直接在第二个树上添加第一个数的元素,时间复杂度为O(NlogN) 就像是合并数组一样合并二叉排序树,分别扫描,时间复杂度极低. 第三种我写了一下 ...