全网整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:400-708-3566

老生常谈java中的fail-fast机制

在JDK的Collection中我们时常会看到类似于这样的话:

例如,ArrayList:

注意,迭代器的快速失败行为无法得到保证,因为一般来说,不可能对是否出现不同步并发修改做出任何硬性保证。快速失败迭代器会尽最大努力抛出 ConcurrentModificationException。因此,为提高这类迭代器的正确性而编写一个依赖于此异常的程序是错误的做法:迭代器的快速失败行为应该仅用于检测 bug。

HashMap中:

注意,迭代器的快速失败行为不能得到保证,一般来说,存在非同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的做法是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测程序错误。

在这两段话中反复地提到”快速失败”。那么何为”快速失败”机制呢?

“快速失败”也就是fail-fast,它是Java集合的一种错误检测机制。当多个线程对集合进行结构上的改变的操作时,有可能会产生fail-fast机制。记住是有可能,而不是一定。例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。

一、fail-fast示例

public class FailFastTest { 
 private static List<Integer> list = new ArrayList<>(); 
  
 /** 
  * @desc:线程one迭代list 
  * @Project:test 
  * @file:FailFastTest.java 
  * @Authro:chenssy 

  */ 
 private static class threadOne extends Thread{ 
  public void run() { 
   Iterator<Integer> iterator = list.iterator(); 
   while(iterator.hasNext()){ 
    int i = iterator.next(); 
    System.out.println("ThreadOne 遍历:" + i); 
    try { 
     Thread.sleep(10); 
    } catch (InterruptedException e) { 
     e.printStackTrace(); 
    } 
   } 
  } 
 } 
  
 /** 
  * @desc:当i == 3时,修改list 
  * @Project:test 
  * @file:FailFastTest.java 
  * @Authro:chenssy 
  * @data:2014年7月26日 
  */ 
 private static class threadTwo extends Thread{ 
  public void run(){ 
   int i = 0 ; 
   while(i < 6){ 
    System.out.println("ThreadTwo run:" + i); 
    if(i == 3){ 
     list.remove(i); 
    } 
    i++; 
   } 
  } 
 } 
  
 public static void main(String[] args) { 
  for(int i = 0 ; i < 10;i++){ 
   list.add(i); 
  } 
  new threadOne().start(); 
  new threadTwo().start(); 
 } 
} 

运行结果:

ThreadOne 遍历:0 
ThreadTwo run:0 
ThreadTwo run:1 
ThreadTwo run:2 
ThreadTwo run:3 
ThreadTwo run:4 
ThreadTwo run:5 
Exception in thread "Thread-0" java.util.ConcurrentModificationException 
 at java.util.ArrayList$Itr.checkForComodification(Unknown Source) 
 at java.util.ArrayList$Itr.next(Unknown Source) 
 at test.ArrayListTest$threadOne.run(ArrayListTest.java:23) 

二、fail-fast产生原因

通过上面的示例和讲解,我初步知道fail-fast产生的原因就在于程序在对 collection 进行迭代时,某个线程对该 collection 在结构上对其做了修改,这时迭代器就会抛出 ConcurrentModificationException 异常信息,从而产生 fail-fast。

要了解fail-fast机制,我们首先要对ConcurrentModificationException 异常有所了解。当方法检测到对象的并发修改,但不允许这种修改时就抛出该异常。同时需要注意的是,该异常不会始终指出对象已经由不同线程并发修改,如果单线程违反了规则,同样也有可能会抛出改异常。

诚然,迭代器的快速失败行为无法得到保证,它不能保证一定会出现该错误,但是快速失败操作会尽最大努力抛出ConcurrentModificationException异常,所以因此,为提高此类操作的正确性而编写一个依赖于此异常的程序是错误的做法,正确做法是:ConcurrentModificationException 应该仅用于检测 bug。下面我将以ArrayList为例进一步分析fail-fast产生的原因。

从前面我们知道fail-fast是在操作迭代器时产生的。现在我们来看看ArrayList中迭代器的源代码:


private class Itr implements Iterator<E> { 
  int cursor; 
  int lastRet = -1; 
  int expectedModCount = ArrayList.this.modCount; 
 
  public boolean hasNext() { 
   return (this.cursor != ArrayList.this.size); 
  } 
 
  public E next() { 
   checkForComodification(); 
   /** 省略此处代码 */ 
  } 
 
  public void remove() { 
   if (this.lastRet < 0) 
    throw new IllegalStateException(); 
   checkForComodification(); 
   /** 省略此处代码 */ 
  } 
 
  final void checkForComodification() { 
   if (ArrayList.this.modCount == this.expectedModCount) 
    return; 
   throw new ConcurrentModificationException(); 
  } 
 } 

从上面的源代码我们可以看出,迭代器在调用next()、remove()方法时都是调用checkForComodification()方法,该方法主要就是检测modCount == expectedModCount ? 若不等则抛出ConcurrentModificationException 异常,从而产生fail-fast机制。所以要弄清楚为什么会产生fail-fast机制我们就必须要用弄明白为什么modCount != expectedModCount ,他们的值在什么时候发生改变的。

expectedModCount 是在Itr中定义的:int expectedModCount = ArrayList.this.modCount;所以他的值是不可能会修改的,所以会变的就是modCount。modCount是在 AbstractList 中定义的,为全局变量:

protected transient int modCount = 0;

那么他什么时候因为什么原因而发生改变呢?请看ArrayList的源码:


public boolean add(E paramE) { 
 ensureCapacityInternal(this.size + 1); 
 /** 省略此处代码 */ 
} 
 
private void ensureCapacityInternal(int paramInt) { 
 if (this.elementData == EMPTY_ELEMENTDATA) 
  paramInt = Math.max(10, paramInt); 
 ensureExplicitCapacity(paramInt); 
} 
 
private void ensureExplicitCapacity(int paramInt) { 
 this.modCount += 1; //修改modCount 
 /** 省略此处代码 */ 
} 
 
ublic boolean remove(Object paramObject) { 
 int i; 
 if (paramObject == null) 
  for (i = 0; i < this.size; ++i) { 
   if (this.elementData[i] != null) 
    continue; 
   fastRemove(i); 
   return true; 
  } 
 else 
  for (i = 0; i < this.size; ++i) { 
   if (!(paramObject.equals(this.elementData[i]))) 
    continue; 
   fastRemove(i); 
   return true; 
  } 
 return false; 
} 
 
private void fastRemove(int paramInt) { 
 this.modCount += 1; //修改modCount 
 /** 省略此处代码 */ 
} 
 
public void clear() { 
 this.modCount += 1; //修改modCount 
 /** 省略此处代码 */ 
} 

从上面的源代码我们可以看出,ArrayList中无论add、remove、clear方法只要是涉及了改变ArrayList元素的个数的方法都会导致modCount的改变。所以我们这里可以初步判断由于expectedModCount 得值与modCount的改变不同步,导致两者之间不等从而产生fail-fast机制。知道产生fail-fast产生的根本原因了,我们可以有如下场景:

有两个线程(线程A,线程B),其中线程A负责遍历list、线程B修改list。线程A在遍历list过程的某个时候(此时expectedModCount = modCount=N),线程启动,同时线程B增加一个元素,这是modCount的值发生改变(modCount + 1 = N + 1)。线程A继续遍历执行next方法时,通告checkForComodification方法发现expectedModCount = N ,而modCount = N + 1,两者不等,这时就抛出ConcurrentModificationException 异常,从而产生fail-fast机制。

所以,直到这里我们已经完全了解了fail-fast产生的根本原因了。知道了原因就好找解决办法了。

三、fail-fast解决办法

通过前面的实例、源码分析,我想各位已经基本了解了fail-fast的机制,下面我就产生的原因提出解决方案。这里有两种解决方案:

方案一:在遍历过程中所有涉及到改变modCount值得地方全部加上synchronized或者直接使用Collections.synchronizedList,这样就可以解决。但是不推荐,因为增删造成的同步锁可能会阻塞遍历操作。

方案二:使用CopyOnWriteArrayList来替换ArrayList。推荐使用该方案。

CopyOnWriteArrayList为何物?ArrayList 的一个线程安全的变体,其中所有可变操作(add、set 等等)都是通过对底层数组进行一次新的复制来实现的。 该类产生的开销比较大,但是在两种情况下,它非常适合使用。1:在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时。2:当遍历操作的数量大大超过可变操作的数量时。遇到这两种情况使用CopyOnWriteArrayList来替代ArrayList再适合不过了。那么为什么CopyOnWriterArrayList可以替代ArrayList呢?

第一、CopyOnWriterArrayList的无论是从数据结构、定义都和ArrayList一样。它和ArrayList一样,同样是实现List接口,底层使用数组实现。在方法上也包含add、remove、clear、iterator等方法。

第二、CopyOnWriterArrayList根本就不会产生ConcurrentModificationException异常,也就是它使用迭代器完全不会产生fail-fast机制。请看:


private static class COWIterator<E> implements ListIterator<E> { 
  /** 省略此处代码 */ 
  public E next() { 
   if (!(hasNext())) 
    throw new NoSuchElementException(); 
   return this.snapshot[(this.cursor++)]; 
  } 
 
  /** 省略此处代码 */ 
 } 

CopyOnWriterArrayList的方法根本就没有像ArrayList中使用checkForComodification方法来判断expectedModCount 与 modCount 是否相等。它为什么会这么做,凭什么可以这么做呢?我们以add方法为例:

public boolean add(E paramE) { 
  ReentrantLock localReentrantLock = this.lock; 
  localReentrantLock.lock(); 
  try { 
   Object[] arrayOfObject1 = getArray(); 
   int i = arrayOfObject1.length; 
   Object[] arrayOfObject2 = Arrays.copyOf(arrayOfObject1, i + 1); 
   arrayOfObject2[i] = paramE; 
   setArray(arrayOfObject2); 
   int j = 1; 
   return j; 
  } finally { 
   localReentrantLock.unlock(); 
  } 
 } 
 
  
 final void setArray(Object[] paramArrayOfObject) { 
  this.array = paramArrayOfObject; 
 } 

CopyOnWriterArrayList的add方法与ArrayList的add方法有一个最大的不同点就在于,下面三句代码:

Object[] arrayOfObject2 = Arrays.copyOf(arrayOfObject1, i + 1); 
arrayOfObject2[i] = paramE; 
setArray(arrayOfObject2); 

就是这三句代码使得CopyOnWriterArrayList不会抛ConcurrentModificationException异常。他们所展现的魅力就在于copy原来的array,再在copy数组上进行add操作,这样做就完全不会影响COWIterator中的array了。

所以CopyOnWriterArrayList所代表的核心概念就是:任何对array在结构上有所改变的操作(add、remove、clear等),CopyOnWriterArrayList都会copy现有的数据,再在copy的数据上修改,这样就不会影响COWIterator中的数据了,修改完成之后改变原有数据的引用即可。同时这样造成的代价就是产生大量的对象,同时数组的copy也是相当有损耗的。

以上这篇老生常谈java中的fail-fast机制就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。


# java  # fail  # fast机制  # Java集合中的fail-fast(快速失败)机制详解  # Java你告诉我 fail-fast 是什么鬼  # 一不小心就让Java开发踩坑的fail-fast是个什么鬼?(推荐)  # Java中fail-fast和fail-safe的使用  # 遍历  # 迭代  # 抛出  # 是在  # 都是  # 于此  # 就在于  # 就会  # 源代码  # 什么时候  # 给大家  # 为例  # 可以看出  # 这么做  # 解决办法  # 根本原因  # 的是  # 而不是  # 这是  # 他们的 


相关文章: 建站之星代理如何优化在线客服效率?  历史网站制作软件,华为如何找回被删除的网站?  如何选择CMS系统实现快速建站与SEO优化?  如何在服务器上配置二级域名建站?  专业网站设计制作公司,如何制作一个企业网站,建设网站的基本步骤有哪些?  专业网站制作服务公司,有哪些网站可以免费发布招聘信息?  电商平台网站制作流程,电商网站如何制作?  制作网站的过程怎么写,用凡科建站如何制作自己的网站?  儿童网站界面设计图片,中国少年儿童教育网站-怎么去注册?  IOS倒计时设置UIButton标题title的抖动问题  如何确保西部建站助手FTP传输的安全性?  定制建站价位费用解析与套餐推荐全攻略  制作旅游网站html,怎样注册旅游网站?  建站之星logo尺寸如何设置最合适?  大连网站设计制作招聘信息,大连投诉网站有哪些?  高防服务器租用指南:配置选择与快速部署攻略  如何用手机制作网站和网页,手机移动端的网站能制作成中英双语的吗?  网站制作大概要多少钱一个,做一个平台网站大概多少钱?  建站主机数据库如何配置才能提升网站性能?  佛山企业网站制作公司有哪些,沟通100网上服务官网?  如何破解联通资金短缺导致的基站建设难题?  c++怎么实现高并发下的无锁队列_c++ std::atomic原子变量与CAS操作【详解】  制作网站的软件下载免费,今日头条开宝箱老是需要下载怎么回事?  如何通过VPS建站实现广告与增值服务盈利?  如何用景安虚拟主机手机版绑定域名建站?  C++ static_cast和dynamic_cast区别_C++静态转换与动态类型安全转换  网站制作服务平台,有什么网站可以发布本地服务信息?  小自动建站系统:AI智能生成+拖拽模板,多端适配一键搭建  公司门户网站制作公司有哪些,怎样使用wordpress制作一个企业网站?  北京营销型网站制作公司,可以用python做一个营销推广网站吗?  如何用PHP快速搭建高效网站?分步指南  洛阳网站制作公司有哪些,洛阳的招聘网站都有哪些?  jQuery 常见小例汇总  建站主机类型有哪些?如何正确选型  成都网站制作价格表,现在成都广电的单独网络宽带有多少的,资费是什么情况呢?  如何在VPS电脑上快速搭建网站?  网站企业制作流程,用什么语言做企业网站比较好?  如何获取免费开源的自助建站系统源码?  公司门户网站制作流程,华为官网怎么做?  如何安全更换建站之星模板并保留数据?  重庆网站制作公司哪家好,重庆中考招生办官方网站?  合肥做个网站多少钱,合肥本地有没有比较靠谱的交友平台?  成都品牌网站制作公司,成都营业执照年报网上怎么办理?  如何快速查询域名建站关键信息?  建站之星好吗?新手能否轻松上手建站?  单页制作网站有哪些,朋友给我发了一个单页网站,我应该怎么修改才能把他变成自己的呢,请求高手指点迷津?  如何快速搭建高效简练网站?  简单实现Android文件上传  如何快速辨别茅台真假?关键步骤解析  零服务器AI建站解决方案:快速部署与云端平台低成本实践 

您的项目需求

*请认真填写需求信息,我们会在24小时内与您取得联系。