CopyOnWriteArrayList设计思路与源码分析
CopyOnWriteArrayList实现了List接口,RandomAccess,Cloneable,Serializable接口。

CopyOnWriteArrayList特性
1、线程安全,在多线程环境下作为共享变量可以放心使用,无需加锁。
2、通过加锁和volatile保证安全
3、每次对数组进行增删改操作都会复制原先元素到新的数组中,在新的数组上进行操作,最后再赋值回去。
他底层使用的数据结构也是数组,对数组中元素的操作都会经历,加锁,拷贝原数组到新数组中,对新数组进行增删改,然后赋值回旧的数组,最后解锁。
除了这些操作外,CopyOnWriteArrayList内的array数组是被volatile和transient修饰的。

类注释
从类注释中,我们可以知道CopyOnWriteArrayList是线程安全的集合类,因为增删改都是在新的数组中进行的,当更新完成后,新数组又被赋值给原先数组,这样所有线程都可以知道哪些元素被修改了。
虽然数组的拷贝开销较大,但是往往比常用的方案效率要好。
在迭代过程中,不会抛出ConcurrentModificationException,因为并不是在原先数组上修改的。
新增
public boolean add(E e) {
final ReentrantLock lock = this.lock;
// 手动加锁
lock.lock();
try {
// 旧数组
Object[] elements = getArray();
// 原先数组长度
int len = elements.length;
// 新数组是旧数组的拷贝,且容量+1
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 直接将新元素e赋值给新数组的最后
newElements[len] = e;
// 设置给原数组array,array = newElements
setArray(newElements);
return true;
} finally {
// 最后释放锁
lock.unlock();
}
}
在整个add的过程中都是加锁的,所以在同一时刻只有一个线程可以add成功,既然已经加锁,那为什么还要创建新数组进行拷贝呢?这是因为原先数组是volatile修饰的,如果我们简单的在原来数组上修改其中某几个元素的值,是无法触发可见性的,我们必须通过修改数组的内存地址才行,也就说要对数组进行重新赋值才行。通过新建一个数组拷贝旧的数组,是可以避免在赋值过程中出现旧数组值被改变的情况。
当在指定位置插入时,如果插入元素的位置时最后一个,那么只需要拷贝一次,如果在中间位置插入时,需要拷贝两次(将数组一分为二);
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();
}
}
删除
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);
// 减一是因为len是从1开始的,index是从0开始的
int numMoved = len - index - 1;
if (numMoved == 0)// 如果删除的是最后一个,直接删除
setArray(Arrays.copyOf(elements, len - 1));
else {
// 如果删除的是中间元素,新建的数组大小-1,并且从0拷贝到删除元素前,从删除元素的后一个拷贝到最后
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();
}
}
删除元素的思路就是
1、加锁
2、根据删除元素的下标确定在数组中的位置,然后采取不同的策略删除
3、解锁
不管事新增还是删除,lock对象都是被final修饰的,他们都共用一把锁,try finally+数组拷贝保证了能够成功删除指定元素。
小结
该集合类的优点是:读取元素不需要加锁,适合读多写少的场景。例如:读取白名单,黑名单等。
缺点也很明显:内存开销比在原数组上修改多了一倍内存占用,可能导致年轻代GC,如果要求强一致性就不能使用这个了。
CopyOnWriteArrayList设计思路与源码分析的更多相关文章
- YYCache设计思路及源码学习
设计思路 利用YYCache来进行操作,实质操作分为了内存缓存操作(YYMemoryCache)和硬盘缓存操作(YYDiskCache).内存缓存设计一般是在内存中开辟一个空间用以保存请求的数据(一般 ...
- CopyOnWriteArrayList实现原理及源码分析
CopyOnWriteArrayList是Java并发包中提供的一个并发容器,它是个线程安全且读操作无锁的ArrayList,写操作则通过创建底层数组的新副本来实现,是一种读写分离的并发策略,我们也可 ...
- asp.net abp模块化开发之通用树2:设计思路及源码解析
一.前言 上一篇大概说了下abp通用树形模块如何使用,本篇主要分析下设计思路. 日常开发中会用到很多树状结构的数据,比如:产品的多级分类.省市区县,大多数系统也会用到类似“通用字典/数据字典”的功能, ...
- Spring Cloud Eureka源码分析之三级缓存的设计原理及源码分析
Eureka Server 为了提供响应效率,提供了两层的缓存结构,将 Eureka Client 所需要的注册信息,直接存储在缓存结构中,实现原理如下图所示. 第一层缓存:readOnlyCache ...
- Android源码分析(五)-----如何从架构师的角度去设计Framework框架
一 : 架构与程序 软件架构是一种思维方式,而程序只是实现思维方式的一种手段,代码固然重要,但是若没有整体的思维架构,一切程序都如水中浮萍. 二 : 框架如何设计 暂时抛开Android Framew ...
- netty源码分析 - Recycler 对象池的设计
目录 一.为什么需要对象池 二.使用姿势 2.1 同线程创建回收对象 2.2 异线程创建回收对象 三.数据结构 3.1 物理数据结构图 3.2 逻辑数据结构图(重要) 四.源码分析 4.2.同线程获取 ...
- Junit 3.8源码分析
JUnit背景介绍 JUnit是由Erich Gamma和Kent Beck 编写的一个回归测试框架(regression testing framework).Junit测试是程序员测试,即所谓的白 ...
- Tomcat详解系列(3) - 源码分析准备和分析入口
Tomcat - 源码分析准备和分析入口 上文我们介绍了Tomcat的架构设计,接下来我们便可以下载源码以及寻找源码入口了.@pdai 源代码下载和编译 首先是去官网下载Tomcat的源代码和二进制安 ...
- Java中ArrayList源码分析
一.简介 ArrayList是一个数组队列,相当于动态数组.每个ArrayList实例都有自己的容量,该容量至少和所存储数据的个数一样大小,在每次添加数据时,它会使用ensureCapacity()保 ...
随机推荐
- BJOI2017 喷式水战改
题目链接. Description 维护一个序列,支持操作: 每次在 \(P_i\) 位置后插入一段 \(X_i\) 单位的燃料,这一段有三个模式,对应的能量分别是 \(A_i, B_i, C_i\) ...
- 从0到1实战移动Web App开发
从0到1实战移动Web App开发 教程介绍 从0到1 实战webapp,通过热门的web前端技术实现移动端app应用,先基础.后实战,在讲解的同时引导思考,会抛出自己独特的观点,一行一行写代码讲 ...
- oracle DG搭建
Oracle DG 搭建1. 环境 OS IP hostname db_name DB_UNIQUE_NAME主库 RHEL 5.4 192.168.12.20 edgzrip1.oracle.com ...
- 11g RAC 集群数据库不能跟随集群启动
1.查看集群资源详细情况 [oracle@rac01-+ASM1 ~]$ crsctl stat res -p 2.修改集群资源ora.rac.db的auto_start属性改为always [ora ...
- Jmeter +Maven+jenkins+eclipse 接口自动化测试
背景: 首先用jmeter录制或者书写性能测试的脚本,用maven添加相关依赖,把性能测试的代码提交到github,在jenkins配置git下载性能测试的代码,配置运行脚本和测试报告,配置运行失败自 ...
- 未雨绸缪,数据保护之NBU介质备份
摘要:华为GaussDB目前已支持NBU介质的备份与恢复,本文主要描述了华为GaussDB数据库和NetBackup对接进行备份.恢复的配置方法与性能调优方法. 一.简介 企业要想避开自然灾难和站点中 ...
- 工具-chrome相关-安装crx包及错误解决(99.3.1)
@ 目录 1.安装教程 2.程序包无效:"CRX_HEADER_INVALID" 1.安装教程 在浏览器上输入 chrome://extensions 并且选择开发者模式 将.cr ...
- Python进阶——为什么GIL让多线程变得如此鸡肋?
本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理 做 Python 开发时,想必你肯定听过 GIL,它经常被 Python 程序员吐槽,说 Pytho ...
- Python简单的验证码生成
用python生成简单的四位数验证码: 1 import random 2 3 if __name__ == "__main__": #这句话简单的理解就是,只有在本文件下以下的代 ...
- JS中Generator的学习小记
Generator的异步实现 整理了一下在学习和使用JS异步过程中的一些知识点.核心是在Generator实例的的回调中调度实例的下一步,同样的思想也能用于其它语言.比如Python中使用Genera ...