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. MySQL的复制:MySQL系列之十三

    一.MySQL复制相关概念 主从复制:主节点将数据同步到多个从节点 级联复制:主节点将数据同步到一个从节点,其他的从节点在向从节点复制数据 同步复制:将数据从主节点全部同步到从节点时才返回给用户的复制 ...

  2. vue2格式化时间戳

    注意:时间戳分为10位和13位的,10位的是秒,13位的是毫秒 这里给出的是格式化13位的方法,10位的时间戳可以加上3个0 <div id="app">{{time ...

  3. [題解](搜索)生日蛋糕(NOI1999)

    搜索剪枝, 1.枚舉上下界: 先$R\subset$$(dep,min(\lfloor\sqrt{n-v}\rfloor,lastr-1))$ 后$H\subset$$(dep,min((n-v)/R ...

  4. HDU 2473 Junk-Mail Filter 并查集,虚拟删除操作

    http://acm.hdu.edu.cn/showproblem.php?pid=2473 给定两种操作 第一种是合并X Y 第二种是把X分离出来,就是从原来的集合中分离出来,其它的关系不变. 关键 ...

  5. Storm概念学习系列 之数据流模型、Storm数据流模型

    不多说,直接上干货! 数据流模型 数据流模型是由数据流.数据处理任务.数据节点.数据处理任务实例等构成的一种数据模型.本节将介绍的数据流模型如图1所示. 分布式流处理系统由多个数据处理节点(node) ...

  6. Java的compare比较

    package com.jckb; public class Name implements Comparable<Name>{ private String firstName; pri ...

  7. 使用Maven运行Java main的3种方式

    使用Maven运行Java main的3种方式 原文  http://blog.csdn.net/qbg19881206/article/details/19850857 主题 Maven maven ...

  8. ssm(Spring、Springmvc、Mybatis)实战之淘淘商城-第十三天(非原创)

    文章大纲 一.课程介绍二.SolrCloud介绍与搭建三.工程部署四.参考资料下载五.参考文章   一.课程介绍 一共14天课程(1)第一天:电商行业的背景.淘淘商城的介绍.搭建项目工程.Svn的使用 ...

  9. java 整型相除得到浮点型

    public class TestFloatOrDouble { public static void main(String[] args) { Point num1 = new Point(84, ...

  10. 解决在eclipse中导入项目名称已存在的有关问题

    新建项目-Import-File System-找到相应的文件夹-Overwrite existing resources without warning打钩,选中项目即可