CopyOnWriteArrayList分析——能解决什么问题
CopyOnWriteArrayList主要可以解决的问题是并发遍历读取无锁(通过Iterator)
对比CopyOnWriteArrayList和ArrayList
假如我们频繁的读取一个可能会变化的清单(数组),你会怎么做?
一个全局的ArrayList(数组),修改时加锁,读取时加锁
读取时为什么需要加锁呢?
如果是ArrayList遍历读取时不加锁,这时其他线程修改了ArrayList(增加或删除),会抛出ConcurrentModificationException,这就是failfast机制(我们这里只讨论Iterator遍历,如果是普通for循环可能会数组越界,这里不讨论)
如果是数组遍历读取时,可能会出现数组越界
所以读锁的是写的操作
如果读加上锁,那么对于并发读来说无疑性能是很糟糕的,当然如果你说用读写锁可以解决这个问题,但是我们这里更期待的是一个无锁的读操作并且能保证线程安全。
下面这个例子营造的背景是相对高并发的读取+相对低并发的修改
List<Integer> arr = new CopyOnWriteArrayList<>();
//List<Integer> arr = new ArrayList<>();//如果通过ArrayList是会报错的
for (int i = 0; i < 3; i++) {
arr.add(i);
}
//多线程读
for (int i = 0; i < 1000; i++) {
final int m = i;
new Thread(() -> {
try {Thread.sleep(1);} catch (InterruptedException e) {}//等等下面写线程的开始
Iterator<Integer> iterator = arr.iterator();
try {Thread.sleep(new Random().nextInt(10));} catch (InterruptedExcep{}//造成不一致的可能性
int count = 0;
while(iterator.hasNext()){
iterator.next();
count++;
}
System.out.println("read:"+count);
}).start();
}
//多线程写
for (int ii = 0; ii < 10; ii++) {
new Thread(() -> {
arr.add(123);
System.out.println("write");
}).start();
}
上面的例子如果更换成ArrayList会报错,原因是:
因为next()方法会调用checkForComodification校验,发现modCount(原始arrayList)与expectedModCount不一致了,这就是上面提到的快速失败,这个快速失败的意思是无论当前是否有并发的情况或问题,只要发现了不一致就抛异常
对于ArrayList解决方案就是遍历iterator时加锁
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
那么为什么换成CopyOnWriteArrayList就可以了呢?我们先不看CopyOnWrite,我们先来分析一下CopyOnWriteArrayList的iterator
public Iterator<E> iterator() {
return new COWIterator<E>(getArray(), 0);
}
CopyOnWriteArrayList 调用iterator时生成的是一个新的数组快照,遍历时读取的是快照,所以永远不会报错(即使读取后修改了列表),并且在CopyOnWriteArrayList是没有fastfail机制的,原因就在于Iterator的快照实现以及CopyOnWrite已经不需要通过fastfail来保证集合的正确性
CopyOnWriteArrayList的CopyOnWrite即修改数组集合时,会重新创建一个数组并对新数据进行调整,调整完成后将新的数组赋值给老的数组
public boolean add(E e) {
final ReentrantLock lock = this.lock;//修改时仍通过可重入锁保证其线程安全
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;//调整新数组
setArray(newElements);//将新的数组赋值给原数组
return true;
} finally {
lock.unlock();
}
}
为什么要拷贝新的数组,这样做有什么好处?
如果不拷贝新的数组(加锁仍保证其线程安全)直接修改原来的数据结构,那么在读的时候就要加锁了,如果读不加锁就有可能读到修改数组的“半成品”(有可能COWIterator<E>(getArray(), 0);就是个半成品)
而拷贝了新的数组,即使修改没有完成,遍历是拿到的也是老的数组,所以不会有问题。
Doug Lea大神在开发这个类的时候也介绍了这个类的主要应用场景是避免对集合的iterator方法加锁遍历,我们来看一下这个类的注释的节选:
* making a fresh copy of the underlying array.This is ordinarily too costly, but may be more efficient
* than alternatives when traversal operations vastly outnumber
* mutations, and is useful when you cannot or don't want to
* synchronize traversals, yet need to preclude interference among
* concurrent threads.
* This array never changes during the lifetime of the
* iterator, so interference is impossible and the iterator is
* guaranteed not to throw {@code ConcurrentModificationException}.
* The iterator will not reflect additions, removals, or changes to
* the list since the iterator was created.
大概翻译一下:
拷贝一个新的数组这看上去太昂贵了,但是遍历数远远超过变更数时却十分有效,并且在你不想使用synchronized遍历时会更有用
这份新拷贝的数组在iterator生命周期永远不会改变,并且在迭代是不会让生ConcurrentModificationException异常
一旦迭代器创建,则迭代器不能够被修改(添加、删除元素)
我们提取一下作者的思想:
1、这个类使用是线程安全的
2、并发通过迭代器遍历不会报错并且无锁
3、在写少读多的前提下,比较合适
CopyOnWriteArrayList分析——能解决什么问题的更多相关文章
- Mybatis关联查询和数据库不一致问题分析与解决
Mybatis关联查询和数据库不一致问题分析与解决 本文的前提是,确定sql语句没有问题,确定在数据库中使用sql和项目中结果不一致. 在使用SpringMVC+Mybatis做多表关联时候,发现也不 ...
- C#中异常:“The type initializer to throw an exception(类型初始值设定项引发异常)”的简单分析与解决方法
对于C#中异常:“The type initializer to throw an exception(类型初始值设定项引发异常)”的简单分析,目前本人分析两种情况,如下: 情况一: 借鉴麒麟.NET ...
- 启动Tomcat一闪而过——分析及解决过程
启动Tomcat一闪而过--分析及解决过程 嗯,昨天将有关JDK的知识稍微整理了一下,现在稍微整理一下有关Tomcat的! 1:Tomcat是什么? Tomcat是当今世界上使用最为广泛的.开源免费的 ...
- SQL Server2005索引碎片分析和解决方法
SQL Server2005索引碎片分析和解决方法 本文作者(郑贤娴),请您在阅读本文时尊重作者版权. 摘要: SQL Server,为了反应数据的更新,需要维护表上的索引,因而这些索引会形成碎片.根 ...
- JavaScript中的ParseInt("08")和“09”返回0的原因分析及解决办法
今天在程序中出现一个bugger ,调试了好久,最后才发现,原来是这个问题. 做了一个实验: alert(parseInt("01")),当这个里面的值为01====>07时 ...
- mybatis异常:Improper inline parameter map format. Should be: #{propName,attr1=val1,attr2=val2}问题分析及解决
转载自:http://blog.csdn.net/jackpk/article/details/44158701 mybatis异常:Improper inline parameter map for ...
- php中session_start()相关问题分析与解决办法
介绍下,在php中使用session时遇到的一些问题,与相关解决方法.1.错误提示Warning: Cannot send session cookie - headers already sentW ...
- 左右c++与java中国的垃圾问题的分析与解决
左右c++与java中国的垃圾问题的分析与解决 DionysosLai(906391500@qq.com) 2014/8/1 问题分析: 之所以会出现中文乱码问题,归根结底在于中文的编码与英文的编码 ...
- 文《左右c++与java中国的垃圾问题的分析与解决》一bug分析
文<左右c++与java中国的垃圾问题的分析与解决>一bug分析 DionysosLai(906391500@qq.com) 2014/10/21 在前几篇一博客<关于c++与jav ...
随机推荐
- vue-cli3.0 脚手架搭建项目
1.安装vue-cli 3.0 npm install -g @vue/cli # or yarn global add @vue/cli 安装成功后查看版本:vue -V(大写的V) 2.命令变化 ...
- boost asio one client one thread
总结了一个简单的boost asio的tcp服务器端与客户端通信流程.模型是一个client对应一个线程.先做一个记录,后续再对此进行优化. 环境:VS2017 + Boost 1.67 serve ...
- 《SQL 进阶教程》 自连接排序
子查询所做的,是计算出价格比自己高的记录的条数并将其作为自己的位次 -- 自连接实现排序功能SELECT P1.name,P1.price,(SELECT COUNT(P2.price)FROM Pr ...
- eclipse对于虚拟内存的溢出处理
第一个配置:-Xms1024m -Xmx2048m 第二个配置: 第二个配置:-XX:MaxPermSize=1024m 第三个配置就是eclipse安装包中的eclipse.ini文件 -Xms51 ...
- 基于CentOS系统下的Oracle的安装
背景 最近的数据库的实验课,要求利用虚拟机安装CentOS系统,并在此系统上安装Oracle_11g软件实现监听,在windows系统上安装SQL Developer软件作为客户端 ,从而可以在SQL ...
- CentOS6.5安装MySQL5.7(也适合其他版本安装)
1.查看是否已经安装过mysql或其依赖,若已装过要先将其删除,否则第4步使用yum安装时会报错: 方法一:yum list installed | grep mysql 方法二:rpm -qa | ...
- js里的数组push用法及append()
result.result[0].name var arr = new Array();$.each(result.result, function(i, item) { arr ...
- Linux网络编程函数
1. Server端-socket/bind/listen/accept/read socket(AF_INET, SOCK_STREAM, 0); //指定端口,内核将端口上的数据转发给该socke ...
- {ldelim},{rdelim} - smarty 内建函数
{ldelim}和{rdelim}用来转义模板的分隔符,缺省为{和}.你也可以用{literal}{/literal}来转义文本块(如Javascript或CSS). 例: {* 在模板外将原样打印分 ...
- java注解相关
本文参考很多大神的文档,特别再次表示感谢分享!! 1.何为注解? 概念:注解(Annotation),也叫元数据.一种代码级别的说明. 它是JDK1.5及以后版本引入的一个特性,与类.接口.枚举是在 ...