浅析CopyOnWriteArrayList
CopyOnWriteArrayList引入
模拟传统的ArrayList出现线程不安全的现象
public class Demo1 {
public static void main(String[] args) {
//List<String> list = new CopyOnWriteArrayList<>();
List<String> list = new ArrayList<>();
//开启50个线程往ArrayList中添加数据
for (int i = 1; i <= 50; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 5));
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
运行结果如下:由于fail-fast机制的存在,抛出了modcount修改异常的错误(modcount是ArrayList源码中的一个变量,用来表示修改的次数,因为ArrayList不是为并发情况而设计的集合类)
如何解决该问题呢?
方式一:可以使用Vector集合,Vector集合是线程安全版的ArrayList,其方法都上了一层synchronized进行修饰,采取jvm内置锁来保证其并发情况下的原子性、可见性、有序性。但同时也带来了性能问题,因为synchronized一旦膨胀到重量级锁,存在用户态到和心态的一个转变,多线程的上下文切换会带来开销。另一个问题是Vector集合的扩容没有ArrayList的策略好
List<String> list = new Vector<>();
方式二:使用Collections.synchronizedList
List<String> list = Collections.synchronizedList(new ArrayList<>());
方式三:采用JUC提供的并发容器,CopyOnWriteArrayList
List<String> list = new CopyOnWriteArrayList<>();
CopyOnWriteArrayList浅析
和ArrayList一样,其底层数据结构也是数组,加上transient不让其被序列化,加上volatile修饰来保证多线程下的其可见性和有序性
先来看看其构造函数是怎么一回事
public CopyOnWriteArrayList() {
//默认创建一个大小为0的数组
setArray(new Object[0]);
}
final void setArray(Object[] a) {
array = a;
}
public CopyOnWriteArrayList(Collection<? extends E> c) {
Object[] elements;
//如果当前集合是CopyOnWriteArrayList的类型的话,直接赋值给它
if (c.getClass() == CopyOnWriteArrayList.class)
elements = ((CopyOnWriteArrayList<?>)c).getArray();
else {
//否则调用toArra()将其转为数组
elements = c.toArray();
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elements.getClass() != Object[].class)
elements = Arrays.copyOf(elements, elements.length, Object[].class);
}
//设置数组
setArray(elements);
}
public CopyOnWriteArrayList(E[] toCopyIn) {
//将传进来的数组元素拷贝给当前数组
setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}
在来看看其读数据的几个操作,可见都没上锁,这就奇怪了,那如何去保证线程安全呢?
final Object[] getArray() {
return array;
}
public int size() {
return getArray().length;
}
public boolean isEmpty() {
return size() == 0;
}
public int indexOf(E e, int index) {
Object[] elements = getArray();
return indexOf(e, elements, index, elements.length);
}
public int lastIndexOf(Object o) {
Object[] elements = getArray();
return lastIndexOf(o, elements, elements.length - 1);
}
........
在来看看其修改时的add函数
public boolean add(E e) {
//使用ReentrantLock上锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
//调用getArray()获取原来的数组
Object[] elements = getArray();
int len = elements.length;
//复制老数组,得到一个长度+1的数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
//添加元素,在用setArray()函数替换原数组
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
可见其修改操作是基于fail-safe机制,像我们的String一样,不在原来的对象上直接进行操作,而是复制一份对其进行修改,另外此处的修改操作是利用Lock锁进行上锁的,所以保证了线程安全问题。
在来看看remove操作,看是不是如此做的
public boolean remove(Object o) {
Object[] snapshot = getArray();
int index = indexOf(o, snapshot, 0, snapshot.length);
return (index < 0) ? false : remove(o, snapshot, 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)
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];
System.arraycopy(current, 0, newElements, 0, index);
System.arraycopy(current, index + 1,
newElements, index,
len - index - 1);
//替换原数组
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
可见其思路是一致的,我们在与ArrayList去对比一下,可见其效率比ArrayList低不少,毕竟多线程场景下,其每次都是要在原数组基础上复制一份在操作耗内存和时间,而ArrayList只是容量满了进行扩容,因此在非多线程的场景下还是用ArrayList吧。
这也解决了我之前的疑问,为啥还学ArrayList呢,JUC版的CopyOnWriteArrayList可以干ArrayList干不了的事,咱们直接用CopyOnWriteArrayList不也挺香。
小结
- CopyOnWriteArrayList适合于多线程场景下使用,其采用读写分离的思想,读操作不上锁,写操作上锁,且写操作效率较低
- CopyOnWriteArrayList基于fail-safe机制,每次修改都会在原先基础上复制一份,修改完毕后在进行替换
- CopyOnWriteArrayList采用的是ReentrantLock进行上锁。
浅析CopyOnWriteArrayList的更多相关文章
- Collections.synchronizedList 、CopyOnWriteArrayList、Vector介绍、源码浅析与性能对比
## ArrayList线程安全问题 众所周知,`ArrayList`不是线程安全的,在并发场景使用`ArrayList`可能会导致add内容为null,迭代时并发修改list内容抛`Concurre ...
- Android AIDL浅析及异步使用
AIDL:Android Interface Definition Language,即 Android 接口定义语言. AIDL 是什么 Android 系统中的进程之间不能共享内存,因此,需要提供 ...
- SQL Server on Linux 理由浅析
SQL Server on Linux 理由浅析 今天的爆炸性新闻<SQL Server on Linux>基本上在各大科技媒体上刷屏了 大家看到这个新闻都觉得非常震精,而美股,今天微软开 ...
- 【深入浅出jQuery】源码浅析--整体架构
最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...
- 高性能IO模型浅析
高性能IO模型浅析 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-blocking ...
- netty5 HTTP协议栈浅析与实践
一.说在前面的话 前段时间,工作上需要做一个针对视频质量的统计分析系统,各端(PC端.移动端和 WEB端)将视频质量数据放在一个 HTTP 请求中上报到服务器,服务器对数据进行解析.分拣后从不同的 ...
- Jvm 内存浅析 及 GC个人学习总结
从诞生至今,20多年过去,Java至今仍是使用最为广泛的语言.这仰赖于Java提供的各种技术和特性,让开发人员能优雅的编写高效的程序.今天我们就来说说Java的一项基本但非常重要的技术内存管理 了解C ...
- 从源码浅析MVC的MvcRouteHandler、MvcHandler和MvcHttpHandler
熟悉WebForm开发的朋友一定都知道,Page类必须实现一个接口,就是IHttpHandler.HttpHandler是一个HTTP请求的真正处理中心,在HttpHandler容器中,ASP.NET ...
- Netty构建分布式消息队列实现原理浅析
在本人的上一篇博客文章:Netty构建分布式消息队列(AvatarMQ)设计指南之架构篇 中,重点向大家介绍了AvatarMQ主要构成模块以及目前存在的优缺点.最后以一个生产者.消费者传递消息的例子, ...
随机推荐
- SpringBoot 整合 MyCat 实现读写分离
MyCat一个彻底开源的,面向企业应用开发的大数据库集群.基于阿里开源的Cobar产品而研发.能满足数据库数据大量存储:提高了查询性能.文章介绍如何实现MyCat连接MySQL实现主从分离,并集成Sp ...
- 解析PE文件
最近在自学解析PE文件,根据小辣椒(CFF Explorer)以及各论坛上大佬的帖子,做了个黑屏打印PE文件的,历时7天完成,在此想跟有相关需要的同学们分享下思路,有不足之处也希望大家不吝赐教,指点出 ...
- Selenium系列(十二) - 自动化必备知识之CSS选择器的详细使用
如果你还想从头学起Selenium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1680176.html 其次,如果你不懂前端基础知识, ...
- ImportError: libpng12.so.0: cannot open shared object file: No such file or directory
原因:动态链接库丢失或路径找不到 解决:执行下列命令即可 wget -q -O /tmp/libpng12.deb http://mirrors.kernel.org/ubuntu/pool/main ...
- 曹工说Redis源码(1)-- redis debug环境搭建,使用clion,达到和调试java一样的效果
概要 最近写了spring系列,这个系列还在进行中,然后有些同学开始叫我大神,然后以为我各方面都比较厉害,当然了,我是有自知之明的,大佬大神什么的,当作一个称呼就好,如果真的以为自己就是大神,那可能就 ...
- [codevs]1250斐波那契数列<矩阵乘法&快速幂>
题目描述 Description 定义:f0=f1=1, fn=fn-1+fn-2(n>=2).{fi}称为Fibonacci数列. 输入n,求fn mod q.其中1<=q<=30 ...
- JS烟花案例
html代码部分 <!DOCTYPE html> <!-- * @Descripttion: * @version: * @Author: 小小荧 * @Date: 2020-03- ...
- 1000行MySQL学习笔记,不怕你不会,就怕你不学!
Windows服务 -- 启动MySQL net start mysql-- 创建Windows服务 sc create mysql binPath= mysqld_bin_path(注意:等号与值之
- vs code 关闭保存自动格式化 formatonsave - [vscode] - [html]
有时候Ctrl+s保存,html代码哥格式会紊乱. 造成这个原因一般是我们基本都在用的一个插件: 访问彼此的私有成员. 示例 #include <iostream> using namespace std; clas ...