Java你告诉我 fail-fast 是什么鬼
01、前言
说起来真特么惭愧:十年IT老兵,Java菜鸟一枚。今天我才了解到Java还有fail-fast一说。不得不感慨啊,学习真的是没有止境。只要肯学,就会有巨多巨多别人眼中的“旧”知识涌现出来,并且在我这全是新的。
能怎么办呢?除了羞愧,就只能赶紧全身心地投入学习,把这些知识掌握。
为了镇楼,必须搬一段英文来解释一下fail-fast。
Insystemsdesign,afail-fastsystemisonewhichimmediatelyreportsatitsinterfaceanyconditionthatislikelytoindicateafailure.Fail-fastsystemsareusuallydesignedtostopnormaloperationratherthanattempttocontinueapossiblyflawedprocess.Suchdesignsoftencheckthesystem'sstateatseveralpointsinanoperation,soanyfailurescanbedetectedearly.Theresponsibilityofafail-fastmoduleisdetectingerrors,thenlettingthenext-highestlevelofthesystemhandlethem.
大家不嫌弃的话,我就用蹩脚的英语能力翻译一下。某场战役当中,政委发现司令员在乱指挥的话,就立马报告给权限更高的中央军委——这样可以有效地避免更严重的后果出现。当然了,如果司令员是李云龙的话,报告也没啥用。
不过,Java的世界里不存在李云龙。fail-fast扮演的就是政委的角色,一旦报告给上级,后面的行动就别想执行。
怎么和代码关联起来呢?看下面这段代码。
publicvoidtest(Wangerwanger){
if(wanger==null){
thrownewRuntimeException("wanger不能为空");
}
System.out.println(wanger.toString());
}
一旦检测到wanger为null,就立马抛出异常,让调用者来决定这种情况下该怎么处理,下一步wanger.toString()就不会执行了——避免更严重的错误出现,这段代码由于太过简单,体现不出来,后面会讲到。
瞧,fail-fast就是这个鬼,没什么神秘的。如果大家源码看得比较多的话,这种例子多得就像旅游高峰期的人头。
然后呢,没了?三秒钟,别着急,我们继续。
02、foreach中集合的remove操作
很长一段时间里,我都不明白为什么不能在foreach循环里进行元素的remove。今天我们就来借机来体验一把。
Listlist=newArrayList<>(); list.add("沉默王二"); list.add("沉默王三"); list.add("一个文章真特么有趣的程序员"); for(Stringstr:list){ if("沉默王二".equals(str)){ list.remove(str); } } System.out.println(list);
这段代码看起来没有任何问题,但运行起来就糟糕了。
Exceptioninthread"main"java.util.ConcurrentModificationException atjava.util.ArrayList$Itr.checkForComodification(ArrayList.java:909) atjava.util.ArrayList$Itr.next(ArrayList.java:859) atcom.cmower.java_demo.str.Cmower3.main(Cmower3.java:14)
为毛呢?
03、分析问题的杀手锏
这时候就只能看源码了,ArrayList.java的909行代码是这样的。
finalvoidcheckForComodification(){
if(modCount!=expectedModCount)
thrownewConcurrentModificationException();
}
也就是说,remove的时候执行了checkForComodification方法,该方法对modCount和expectedModCount进行了比较,发现两者不等,就抛出了ConcurrentModificationException异常。
可为什么会执行checkForComodification方法呢?这就需要反编译一下foreach那段代码了。
Listlist=newArrayList(); list.add("沉默王二"); list.add("沉默王三"); list.add("一个文章真特么有趣的程序员"); Iteratorvar3=list.iterator(); while(var3.hasNext()){ Stringstr=(String)var3.next(); if("沉默王二".equals(str)){ list.remove(str); } } System.out.println(list);
原来foreach是通过迭代器Iterator配合while循环实现的。
1)ArrayList.iterator()返回的Iterator其实是ArrayList的一个内部类Itr。
publicIteratoriterator(){ returnnewItr(); }
Itr实现了Iterator接口。
privateclassItrimplementsIterator{ intcursor;//indexofnextelementtoreturn intlastRet=-1;//indexoflastelementreturned;-1ifnosuch intexpectedModCount=modCount; Itr(){} publicbooleanhasNext(){ returncursor!=size; } @SuppressWarnings("unchecked") publicEnext(){ checkForComodification(); inti=cursor; Object[]elementData=ArrayList.this.elementData; if(i>=elementData.length) thrownewConcurrentModificationException(); cursor=i+1; return(E)elementData[lastRet=i]; } }
也就是说newItr()的时候expectedModCount被赋值为modCount,而modCount是List的一个成员变量,表示集合被修改的次数。由于list此前执行了3次add方法,所以modCount的值为3;expectedModCount的值也为3。
可当执行list.remove(str)后,modCount的值变成了4。
privatevoidfastRemove(intindex){
modCount++;
intnumMoved=size-index-1;
if(numMoved>0)
System.arraycopy(elementData,index+1,elementData,index,
numMoved);
elementData[--size]=null;//cleartoletGCdoitswork
}
注:remove方法内部调用了fastRemove方法。
下一次循环执行到Stringstr=(String)var3.next();的时候,就会调用checkForComodification方法,此时一个为3,一个为
4,就只好抛出异常ConcurrentModificationException了。
不信,可以直接在ArrayList类的909行打个断点debug一下。
真的耶,一个是4一个是3。
总结一下。在foreach循环中,集合遍历其实是通过迭代器Iterator配合while循环实现的,但是元素的remove却直接使用的集合类自身的方法。这就导致Iterator在遍历的时候,会发现元素在自己不知情的情况下被修改了,它觉得很难接受,就抛出了异常。
读者朋友们,你们是不是觉得我跑题了,fail-fast和foreach中集合的remove操作有什么关系呢?
有!Iterator使用了fail-fast的保护机制。
04、怎么避开fail-fast保护机制呢
通过上面的分析,相信大家都明白为什么不能在foreach循环里进行元素的remove了。
那怎么避开fail-fast保护机制呢?毕竟删除元素是常规操作,咱不能因噎废食啊。
1)remove后break
Listlist=newArrayList<>(); list.add("沉默王二"); list.add("沉默王三"); list.add("一个文章真特么有趣的程序员"); for(Stringstr:list){ if("沉默王二".equals(str)){ list.remove(str); break; } }
我怎么这么聪明,忍不住骄傲一下。有读者不明白为什么吗?那我上面的源码分析可就白分析了,爬楼再看一遍吧!
略微透露一下原因:break后循环就不再遍历了,意味着Iterator的next方法不再执行了,也就意味着checkForComodification方法不再执行了,所以异常也就不会抛出了。
但是呢,当List中有重复元素要删除的时候,break就不合适了。
2)for循环
Listlist=newArrayList<>(); list.add("沉默王二"); list.add("沉默王三"); list.add("一个文章真特么有趣的程序员"); for(inti=0,n=list.size();i for循环虽然可以避开fail-fast保护机制,也就说remove元素后不再抛出异常;但是呢,这段程序在原则上是有问题的。为什么呢?
第一次循环的时候,i为0,list.size()为3,当执行完remove方法后,i为1,list.size()却变成了2,因为list的大小在remove后发生了变化,也就意味着“沉默王三”这个元素被跳过了。能明白吗?
remove之前list.get(1)为“沉默王三”;但remove之后list.get(1)变成了“一个文章真特么有趣的程序员”,而list.get(0)变成了“沉默王三”。
3)Iterator
Listlist=newArrayList<>(); list.add("沉默王二"); list.add("沉默王三"); list.add("一个文章真特么有趣的程序员"); Iterator itr=list.iterator(); while(itr.hasNext()){ Stringstr=itr.next(); if("沉默王二".equals(str)){ itr.remove(); } } 为什么使用Iterator的remove方法就可以避开fail-fast保护机制呢?看一下remove的源码就明白了。
publicvoidremove(){ if(lastRet<0) thrownewIllegalStateException(); checkForComodification(); try{ ArrayList.this.remove(lastRet); cursor=lastRet; lastRet=-1; expectedModCount=modCount; }catch(IndexOutOfBoundsExceptionex){ thrownewConcurrentModificationException(); } }虽然删除元素依然使用的是ArrayList的remove方法,但是删除完会执行expectedModCount=modCount,保证了expectedModCount与modCount的同步。
05、最后
在Java中,fail-fast从狭义上讲是针对多线程情况下的集合迭代器而言的。这一点可以从ConcurrentModificationException定义上看得出来。
Thisexceptionmaybethrownbymethodsthathavedetectedconcurrent
modificationofanobjectwhensuchmodificationisnotpermissible.Forexample,itisnotgenerallypermissibleforonethreadtomodifyaCollectionwhileanotherthreadisiteratingoverit.Ingeneral,theresultsoftheiterationareundefinedunderthesecircumstances.SomeIteratorimplementations(includingthoseofallthegeneralpurposecollectionimplementationsprovidedbytheJRE)maychoosetothrowthisexceptionifthisbehaviorisdetected.Iteratorsthatdothisareknownasfail-fastiterators,astheyfailquicklyandcleanly,ratherthatriskingarbitrary,non-deterministicbehavioratanundeterminedtimeinthefuture.
再次拙劣地翻译一下。
该异常可能由于检测到对象在并发情况下被修改而抛出的,而这种修改是不允许的。
通常,这种操作是不允许的,比如说一个线程在修改集合,而另一个线程在迭代它。这种情况下,迭代的结果是不确定的。如果检测到这种行为,一些Iterator(比如说ArrayList的内部类Itr)就会选择抛出该异常。这样的迭代器被称为fail-fast迭代器,因为尽早的失败比未来出现不确定的风险更好。
既然是针对多线程,为什么我们之前的分析都是基于单线程的呢?因为从广义上讲,fail-fast指的是当有异常或者错误发生时就立即中断执行的这种设计,从单线程的角度去分析,大家更容易明白。
你说对吗?
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。