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分析——能解决什么问题的更多相关文章

  1. Mybatis关联查询和数据库不一致问题分析与解决

    Mybatis关联查询和数据库不一致问题分析与解决 本文的前提是,确定sql语句没有问题,确定在数据库中使用sql和项目中结果不一致. 在使用SpringMVC+Mybatis做多表关联时候,发现也不 ...

  2. C#中异常:“The type initializer to throw an exception(类型初始值设定项引发异常)”的简单分析与解决方法

    对于C#中异常:“The type initializer to throw an exception(类型初始值设定项引发异常)”的简单分析,目前本人分析两种情况,如下: 情况一: 借鉴麒麟.NET ...

  3. 启动Tomcat一闪而过——分析及解决过程

    启动Tomcat一闪而过--分析及解决过程 嗯,昨天将有关JDK的知识稍微整理了一下,现在稍微整理一下有关Tomcat的! 1:Tomcat是什么? Tomcat是当今世界上使用最为广泛的.开源免费的 ...

  4. SQL Server2005索引碎片分析和解决方法

    SQL Server2005索引碎片分析和解决方法 本文作者(郑贤娴),请您在阅读本文时尊重作者版权. 摘要: SQL Server,为了反应数据的更新,需要维护表上的索引,因而这些索引会形成碎片.根 ...

  5. JavaScript中的ParseInt("08")和“09”返回0的原因分析及解决办法

    今天在程序中出现一个bugger ,调试了好久,最后才发现,原来是这个问题. 做了一个实验: alert(parseInt("01")),当这个里面的值为01====>07时 ...

  6. 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 ...

  7. php中session_start()相关问题分析与解决办法

    介绍下,在php中使用session时遇到的一些问题,与相关解决方法.1.错误提示Warning: Cannot send session cookie - headers already sentW ...

  8. 左右c++与java中国的垃圾问题的分析与解决

    左右c++与java中国的垃圾问题的分析与解决 DionysosLai(906391500@qq.com)  2014/8/1 问题分析: 之所以会出现中文乱码问题,归根结底在于中文的编码与英文的编码 ...

  9. 文《左右c++与java中国的垃圾问题的分析与解决》一bug分析

    文<左右c++与java中国的垃圾问题的分析与解决>一bug分析 DionysosLai(906391500@qq.com) 2014/10/21 在前几篇一博客<关于c++与jav ...

随机推荐

  1. Bitbucekt Reference

    Bitbucket Server installation guide https://confluence.atlassian.com/bitbucketserver/bitbucket-serve ...

  2. [BJWC2008]雷涛的小猫 dp

    题目背景 原最大整数参见P1012 题目描述 雷涛同学非常的有爱心,在他的宿舍里,养着一只因为受伤被救助的小猫(当然,这样的行为是违反学生宿舍管理条例的).在他的照顾下,小猫很快恢复了健康,并且愈发的 ...

  3. POJ1321-棋盘问题

    题目链接:点击打开链接 Description 在一个给定形状的棋盘(形状可能是不规则的)上面摆放棋子,棋子没有区别.要求摆放时任意的两个棋子不能放在棋盘中的同一行或者同一列,请编程求解对于给定形状和 ...

  4. require、require_once、include、include_once

    在 PHP 中,您可以在服务器执行 PHP 文件之前在该文件中插入一个文件的内容. include 和 require 语句用于在执行流中插入写在其他文件中的有用的代码. include 和 requ ...

  5. myeclipse svn 在线安装

    https://www.cnblogs.com/sxdcgaq8080/p/6000446.html http://subclipse.tigris.org/update_1.8.x

  6. emmet高级技巧

    编写好HTML和CSS代码时,我们也需要修改或添加一些内容,Emmet提供了很多非常独特的工具,可以大大提高编辑体验,下面我们挑选几个常用的功能来介绍. 萨龙龙发现在sublime text中安装的E ...

  7. HDU 5792 L - World is Exploding 。容斥原理 + 树状数组 + 离散化

    题目,要求找出有多少对这样的东西,四个数,并且满足num[a]<num[b] &&num[c]>num[d] 要做这题,首先要懂得用树状数组,我设,下面的小于和大于都是严格 ...

  8. Hadoop完全分布式环境下,DataNode进程正常启动,但是网页上不显示DataNode节点

    Hadoop完全分布式环境下,上传文件到hdfs上时报错: // :: WARN hdfs.DFSClient: DataStreamer Exception org.apache.hadoop.ip ...

  9. C++中的swap函数

    最通用的模板交换函数模式:创建临时对象,调用对象的赋值操作符 template <class T> void swap ( T& a, T& b ) { T c(a); a ...

  10. TemplateBinding与Binding区别,以及WPF自定义控件开发的遭遇

    在上一次的文章WPF OnApplyTemplate 不执行 或者执行滞后的疑惑谈到怎么正确的开发自定义控件,我们控件的样式中,属性的绑定一般都是用TemplateBinding来完成,如下一个基本的 ...