fail-fast机制及CopyOnWriteArrayList的原理

目录

先看一个例子

class Te1 extends Thread
{
private List<Integer> list; public Te1(List<Integer> list)
{
this.list = list;
} public void run()
{
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
int i = iterator.next();
}
}
} class Te2 extends Thread
{
private List<Integer> list; public Te2(List<Integer> list)
{
this.list = list;
}
public void run()
{
for (int i = 0; i < list.size(); i++)
{
list.remove(i);
}
}
}
public class Test {
public static void main(String[] args) {
ArrayList<Integer> list=new ArrayList();
for (int i = 0; i <100 ; i++) {
list.add(i);
}
Te1 t1=new Te1(list);
Te2 t2=new Te2(list);
t1.start();
t2.start(); }
}
  • 一个线程迭代,一个线程进行删除,运行时抛出ConcurrentModificationException异常

ConcurrentModificationException

  • 中文意思为并发修改异常
736     public Iterator<E> iterator() {
737 return new Itr();
738 }
743 private class Itr implements Iterator<E> {
744 int cursor; // index of next element to return
745 int lastRet = -1; // index of last element returned; -1 if no such
746 int expectedModCount = modCount;
747
748 public boolean hasNext() {
749 return cursor != size;
750 }
751
752 @SuppressWarnings("unchecked")
753 public E next() {
754 checkForComodification();
...
763 }
764
765 public void remove() {
766 if (lastRet < 0)
767 throw new IllegalStateException();
768 checkForComodification();
...
778 }
779
780 final void checkForComodification() {
781 if (modCount != expectedModCount)
782 throw new ConcurrentModificationException();
783 }
784 }
  • ArrayList有一个内部类Itr,从源码可以看到这个类的next和remove方法里面都调用了一个chechForModification方法,而从这个方法(780行)的源码可以看到,他是通过判断modCount和expectedModCount是否相等来决定是否抛出并发修改异常
  • 同时在这个内部类可以看expectedModCount初始化为modCount(746行),后面并没有修改
377     public boolean add(E e) {
378 ensureCapacity(size + 1); // Increments modCount!!
...
381 }
178 public void ensureCapacity(int minCapacity) {
179 modCount++;
180 ...
189 }
439 public boolean remove(Object o) {
440 if (o == null) {
441 for (int index = 0; index < size; index++)
442 if (elementData[index] == null) {
443 fastRemove(index);
444 return true;
445 }
446 } else {
447 for (int index = 0; index < size; index++)
448 if (o.equals(elementData[index])) {
449 fastRemove(index);
450 return true;
451 }
452 }
453 return false;
454 }
460 private void fastRemove(int index) {
461 modCount++;
...
467 }
  • 从ArrayList的add和remove方法源码可以看到,这两个方法都会导致modCount的改变
  • 那么可以分析为什么之前的代码会抛出异常,线程A进行迭代,此时expectedModCount已经确定了,后面并没有进行修改,而此时线程B同时remove,从前面知道remove会导致modCount改变,此时两者不同导致抛出异常

fail-fast

A fail-fast system is nothing but immediately report any failure that is likely to lead to failure. When a problem occurs, a fail-fast system fails immediately.
In Java, we can find this behavior with iterators. In case, you have called iterator on a collection object, and another thread tries to modify the collection object, then concurrent modification exception will be thrown. This is called fail-fast.
  • 中文译为快速失败,这是一种错误检测机制。
  • 对上文进行翻译,当在对一个集合进行迭代的时候,其他线程尝试去修改这个集合,并发修改异常会被抛出。这就叫做快速失败。

CopyOnWriteArrayList

  • CopyOnWriteArrayList可以解决fail-fast的问题,将ArrayList替换成CopyWriteArrayList进行试验。
public class Test {
public static void main(String[] args) {
CopyOnWriteArrayList<Integer> list=new CopyOnWriteArrayList();
for (int i = 0; i <100 ; i++) {
list.add(i);
}
Te1 t1=new Te1(list);
Te2 t2=new Te2(list);
t1.start();
t2.start(); }
  • 结果发现并没有抛出异常,下面从源码角度来分析
  • CopyOnWriteArrayList的remove方法
469     public E remove(int index) {
470 final ReentrantLock lock = this.lock;
471 lock.lock();
472 try {
473 Object[] elements = getArray();
474 int len = elements.length;
475 E oldValue = get(elements, index);
476 int numMoved = len - index - 1;
477 if (numMoved == 0)
478 setArray(Arrays.copyOf(elements, len - 1));
479 else {
480 Object[] newElements = new Object[len - 1];
481 System.arraycopy(elements, 0, newElements, 0, index);
482 System.arraycopy(elements, index + 1, newElements, index,
483 numMoved);
484 setArray(newElements);
485 }
486 return oldValue;
487 } finally {
488 lock.unlock();
489 }
490 }
99 final void setArray(Object[] a) {
100 array = a;
101 }
  • 473行获取当前的Object数组,480行创建一个新的Object数组,再将旧的数组复制到新的数组上,484行将array指向新的数组
956     public Iterator<E> iterator() {
957 return new COWIterator<E>(getArray(), 0);
958 }
991 private static class COWIterator<E> implements ListIterator<E> {
992
993 private final Object[] snapshot;
994
995 private int cursor;
996
997 private COWIterator(Object[] elements, int initialCursor) {
998 cursor = initialCursor;
999 snapshot = elements;
1000 }
1001
1002 public boolean hasNext() {
1003 return cursor < snapshot.length;
1004 }
1005
1010 @SuppressWarnings("unchecked")
1011 public E next() {
1012 if (! hasNext())
1013 throw new NoSuchElementException();
1014 return (E) snapshot[cursor++];
1015 }
1016
  • 999行将snapshot指向当前的array
  • 1011行执行next方法返回snapshot中元素,那么在遍历的过程,如果其他线程执行remove并将array指向了新创建的数组,这个snapshot并没有更新为新的数组,仍然指向的是remove之前的数组
  • 从CopyOnWriteArrayList的迭代器也可以发现没有fail-fast机制.

CopyOnWriteArrayList分析

  • 修改代价大,可以从源码知道,remove还是add方法,都会进行一次数组的复制,这样消耗了空间(可能导致gc的频率提高)也消耗了时间
  • 读写分离,读写不一致,读的时候读的是旧的数组,写的时候写的是新的数组,所以读的时候不一定是最新的
  • 读的时候不需要进行加锁,因为写的时候是写在新的数组,读的数组是旧的数组,并不会改变
  • 因此,CopyOnWriteArrayList适合读多写少的场景

我觉得分享是一种精神,分享是我的乐趣所在,不是说我觉得我讲得一定是对的,我讲得可能很多是不对的,但是我希望我讲的东西是我人生的体验和思考,是给很多人反思,也许给你一秒钟、半秒钟,哪怕说一句话有点道理,引发自己内心的感触,这就是我最大的价值。(这是我喜欢的一句话,也是我写博客的初衷)

作者:jiajun 出处: http://www.cnblogs.com/-new/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。如果觉得还有帮助的话,可以点一下右下角的【推荐】,希望能够持续的为大家带来好的技术文章!想跟我一起进步么?那就【关注】我吧。

java基础解析系列(八)---fail-fast机制及CopyOnWriteArrayList的原理的更多相关文章

  1. java基础解析系列(九)---String不可变性分析

    java基础解析系列(九)---String不可变性分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---In ...

  2. java基础解析系列(十)---ArrayList和LinkedList源码及使用分析

    java基础解析系列(十)---ArrayList和LinkedList源码及使用分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder jav ...

  3. java基础解析系列(十一)---equals、==和hashcode方法

    java基础解析系列(十一)---equals.==和hashcode方法 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系 ...

  4. java基础解析系列(七)---ThreadLocal原理分析

    java基础解析系列(七)---ThreadLocal原理分析 目录 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)-- ...

  5. java基础解析系列(二)---Integer

    java基础解析系列(二)---Integer 前言:本系列的主题是平时容易疏忽的知识点,只有基础扎实,在编码的时候才能更注重规范和性能,在出现bug的时候,才能处理更加从容. 目录 java基础解析 ...

  6. java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现

    java基础解析系列(四)---LinkedHashMap的原理及LRU算法的实现 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析 ...

  7. java基础解析系列(五)---HashMap并发下的问题以及HashTable和CurrentHashMap的区别

    java基础解析系列(五)---HashMap并发下的问题以及HashTable和CurrentHashMap的区别 目录 java基础解析系列(一)---String.StringBuffer.St ...

  8. java基础解析系列(六)---深入注解原理及使用

    java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer ja ...

  9. java基础解析系列(六)---注解原理及使用

    java基础解析系列(六)---注解原理及使用 java基础解析系列(一)---String.StringBuffer.StringBuilder java基础解析系列(二)---Integer缓存及 ...

随机推荐

  1. Spring Cloud官方文档中文版-服务发现:Eureka服务端

    官方文档地址为:http://cloud.spring.io/spring-cloud-static/Dalston.SR3/#spring-cloud-eureka-server 文中例子我做了一些 ...

  2. 初学者入门web前端:C#基础知识:函数

    入行前端对函数的掌握程度有可能直接影响以后工作的效率,使用函数可以高效的编写编码,节省时间,所以我整理了C#中最基础的函数知识点,虽然我在学习中 遇到很多问题,但是只要能够解决这些问题,都是好的. 一 ...

  3. IBM与麻省理工学院联合建立AI实验室 承诺投资2.4亿美元

         IBM和麻省理工学院将通过今天宣布的一个新的联合实验室共同努力,对人工智能进行广泛的研究.麻省理工学院IBM沃森AI实验室将重点关注四个研究支柱:开发AI算法,使用物理学来创建AI计算的新硬 ...

  4. 【C++小白成长撸】--N阶幻方(魔阵)矩阵

    解决方法:1.第一个元素放在第一行中间一列 2.下一个元素存放在当前元素的上一行.下一列. 3.如果上一行.下一列已经有内容,则下一个元素的存放位置为当前列的下一行. 在找上一行.下一行或者下一列的时 ...

  5. LINUX下C语言编程调用函数、链接头文件以及库文件

    LINUX下C语言编程经常需要链接其他函数,而其他函数一般都放在另外.c文件中,或者打包放在一个库文件里面,我需要在main函数中调用这些函数,主要有如下几种方法: 1.当需要调用函数的个数比较少时, ...

  6. 所谓编码--泛谈ASCII、Unicode、UTF8、UTF16、UCS-2等编码格式

    最近在看nodejs的源码,看到stream的实现里面满地都是encoding,不由想起以前看过的一篇文章--在前面的随笔里面有提到过--阮一峰老师的<字符编码笔记:ASCII,Unicode和 ...

  7. 【★】深入BGP原理和思想【第一部】

    前言:学思科技术我想说,浅尝辄止,不是天才千万别深钻.和我研究高等数学一样,越深入就会发现越多的问题与不合理之处.尤其对于IT界,算法的最终解释权还是掌握在老外手中,所以对于有些细节,我们" ...

  8. 团队作业8——第二次项目冲刺(Beta阶段)

    Deadline: 2017-5-28 22:00PM,以博客发表日期为准 评分基准: 按时交 - 有分,检查的项目包括后文的三个个方面 冲刺计划安排(单独1篇博客) 七天的敏捷冲刺(每天发布1篇,共 ...

  9. Java-判断一个数是不是素数

    import java.util.Scanner; /** * @author 薛定谔的猫 * java判断一个数是不是素数 * * 素数又称质数,是指在一个大于1的自然数中,除了1和本身之外,不能被 ...

  10. linux fork两次避免僵尸进程的程序(简单)

    #include<stdio.h> #include<unistd.h> int main() { pid_t pid,pid1; int status; ) { printf ...