今天偶然听到同事提起fail-fast机制,也说这是所有程序员都会碰到的一个问题,but非常尴尬的就是我真的没有碰到过,也觉得自己好像真的是井底之蛙,很多很多知识点都不知道。当然,我也觉得,一个人除了主动学习之外,其他的知识点都是要通过日常碰到来进行解决以及学习。今天既然听到了,当然必须来进行一下学习。
首先贴出代码块,fail-fast可能发生在单线程环境下,或者多线程环境下。
我们先列出单线程环境下可能出现的一种场景。
运行后产生的结果如下
多线程环境下
运行后产生结果如下
首先我们看异常信息,concurrentModficationException,顾名思义,并发修改异常,而异常发生的地方在ArrayList的匿名类Itr.next方法中的checkForComodification处。其实通过两个不同的例子我们可以发现,其中最主要的原因就是进行了remove操作,而为什么做了remove之后就会产生这样的快速失败呢。
根据以上信息,我找到了对应抛出异常的代码段,进行分析。如下图所示。
根据代码段中,我们可以看到抛出异常的主要原因就是modCount 与 expectedModCount不一致了,进而导致出现了并发修改异常,那么这两个值到底是什么东西呢?这就要向上追溯,看下ArrayList中的Itr匿名类对这两个属性的定义了。
那么在这里我也解释一下itr类是个什么东西,其实它就是由ArrayList自己所实现的Iterator,而我们在使用Iterator时,ArrayList会返回给我们一个自己实现的Itr内部类,这个内部类包含了ArrayList自己所实现的遍历数组方法。也就是在这里,发生了异常。放出源码。
通过初始化我们可以发现,expectedModCount就是ArrayList中的modCount,那这个modCount其实读过ArrayList源码的人都知道这个值是什么,但是可能有些小白真的没有见过,所以我们也拉出源码看一下。
在这我找到了最常见的三处修改modCount的地方,remove,clear,add的三个操作都对其modCount进行了加一操作,当然,不止这三处对modCount进行增加,还有很多地方的操作都会使其加一,值进行改变。也就是说,modCount记录了这个ArryList的变动次数,当list进行了某些修改内容的操作后,便会记录这一次的值。
我们再返回头去看错误处其实就很清晰了,我们因为在线程或者遍历中,对数组进行了remove操作,而只要执行了remove,就会使modcount加一,导致最初被赋值的exctepModCount与当前数组的modCount不一致了,那么在遍历过程中,itr都会先进行check,在check时发现这两个值不一致了,所以会产生concurrentModficationException这样的并发修改异常。
哈哈哈没想到吧,在结尾处我胡汉三又回来了,2020.4.24我参加了一个面试,被对方问起了fail-fast,以此考验我对这个知识点了解是否深刻。我真儿真儿没想到真的有如此棒的面试官会去翻阅我的博客,感到荣幸至极哈哈。
不过啊,我也通过面试官的询问,发现了自己对这个机制还有未了解清晰的地方与内容,在此再次感激技术官的提问,使我知道自己的不足之处。
面试官提问:
(1)既然你说modcount是控制是否扔出异常的,那for循环的时候做add操作是否也会抛出异常呢。
当然对于这个问题我肯定是不清晰的,我是回复说不会的,但是反观既然我add时候会修改modcount,那岂不是在for循环时add也会抛出fail-fast???知道自己答错了,但也没想出到底是为什么。后续进行查询发现大错特错哈哈哈哈,感谢面试官。
首先我们要知道iterator与普通for循环的区别。
iterator是通过容器自己实现的内部类,我们想做迭代操作就必须获取相应容器实现的itr对象,再通过其内部实现去迭代数据元素。而for循环是针对底层数组下标直接进行操作。故此,对于普通的for循环进行add操作并不会去检查相应的modcount是否发生了变动。相对的,itr自己内部实现了容器迭代的逻辑,在容器循环过程中一旦发生了add、remove、等使modcount发生变动的操作时,便会抛出fail-fast异常。这种小细节只能怪自己没有认真思考与阅读,不能去怪任何人,反而要感激他人的提问。
(2)fail-fast在单线程环境下是否会产生。
这个答案我也回答错误了,当时认为单线程中不会有这样的错误抛出,实际上并不是这样的。
即使在单线程环境下,我们在利用iterator进行迭代的时候也会相应的改变其modcount指针,这个数值一旦被改变了,对于其他需要checkmodcount的方法来说,都会产生fail-fast异常。并不单单是在多线程环境下会产生这样的错误。
文章是小编辛辛苦苦敲出来的,拿走的记得标明原作者哦。