浅析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主要构成模块以及目前存在的优缺点.最后以一个生产者.消费者传递消息的例子, ...
随机推荐
- 四、用户交互(输入input,格式化输出)与运算符
1.接收用户的输入 在Python3:input会将用户输入的所有内容都存成字符串类型 列: username = input("请输入您的账号:") # "egon&q ...
- 02 Python函数与模块
• 函数是什么 print() input() format() list() lower() upper() • 函数的定义 def 函数名(形参1,2,3) 函数体 return • 函数 ...
- excel中存储的icount,赋值完之后
最近需要实现一个功能,为了确保每次函数运行的时候count是唯一的,所以想读取excel中存储的icount,赋值完之后对其进行+1操作,并存入excel文件,确保下次读取的count是新的,没有出现 ...
- [LeetCode] 937. Reorder Data in Log Files 日志文件的重新排序
You have an array of `logs`. Each log is a space delimited string of words. For each log, the first ...
- C# 添加、修改、删除PPT中的超链接
本文介绍通过C# 编程如何在PPT幻灯片中添加超链接的方法,添加链接时,可给文本或者图片添加超链接,链接对象可指向网页地址.邮件地址.指定幻灯片等,此外,也可以参考文中编辑.删除幻灯片中已有超链接的方 ...
- A song for a new begining 8月26日到10月11日 第一阶段
- qt creator源码全方面分析(3-6)
目录 qtcreatorplugin.pri 使用实例 上半部 下半部 结果 qtcreatorplugin.pri 上一节我们介绍了qtcreatorlibrary.pri,现在我们介绍qtcrea ...
- 解决 node-sass 安装失败
在项目下新建.npmrc文件内容如下: sass_binary_site=https://npm.taobao.org/mirrors/node-sass/ phantomjs_cdnurl=http ...
- iOS、Android 开发的前景真的那么差吗?
程序员问问周刊:别蕉绿了,问问「IT老兵哥」吧! Q:本科 985 在上海二线互联网企业工作,公司有人才落户指标,但要排队等五年左右.除此外只能等居住满七年落户,现在想换工作追求更好的发展,纠结是继续 ...
- 手动搭建I/O网络通信框架1:Socket和ServerSocket入门实战,实现单聊
资料:慕课网 第二章:手动搭建I/O网络通信框架2:Socket和ServerSocket入门实战,实现单聊 这个基础项目会作为BIO.NIO.AIO的一个前提,后面会有数篇博客会基于这个小项目利用B ...