博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
java基础提高之 fail-fast
阅读量:5913 次
发布时间:2019-06-19

本文共 6486 字,大约阅读时间需要 21 分钟。

        在查看集合类源码时,fail-fast这个词出现的频率很高,几乎每一个集合类中都会出现,比如ArrayList、HashMap、HashSet、LinkedHashMap等。这一篇文章我们将来讨论一下什么是fail-fast以及fail-fast的实现原理。

什么是fail-fast

        fail-fast(快速失败机制)是java集合中的一种错误机制。在HashMap中有这么一段描述fail-fast的机制的注释文字:

The iterators returned by all of this class's "collection view methods" are fail-fast: if the map is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove method, the iterator will throw a {@link ConcurrentModificationException}. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.

        上面这段英文告诉了我们fail-fast是如何出现的,其中提到当一个迭代器被创建(这里我们单纯的认为是调用了HashMap的keySet()、values()等方法后根据返回的结果又调用了iterator方法)后,如果map的结构被修改(此时的结构修改是指map的size发生变化,比如扩容、新建或者删除了其中的元素,更新不算结构修改)。那么在返回的iterator中进行遍历会抛出一个 ConcurrentModificationException错误。这就是快速失败机制。因此,在并发修改的情况下,迭代器快速而干净的失败,而不是在未来的未确定时间冒着任意的非确定行为的风险。

        对于为何对原集合类操作会影响到之前返回的iterator我们会在下面进行说明。

        另外在HashMap解释完fail-fast后又紧跟着来了这么一段:

Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.

        这段的大致意思是,迭代器的fail-fast机制并不能被保证一定会发生,一般来说,在存在不同步的并发修改时不可能做出任何有力保证,但是fail-fast会尽最大努力抛出ConcurrentModificationException错误。因此如果一个程序依赖这个异常去保证其运行的正确性是错误的,快速失败机制只能用来标识这个错误。

        在源码中就比如HashMap的keySet()方法,在注释中明确提出了,这个方法返回的集合由HashMap支持,所以任何对map的更改的结果都会反映到返回的set上,反之也一样。我们通过代码来验证这一段话

package com.lichaobao.collectionsown.collections;import java.util.*;import java.util.stream.Stream;/** * @author lichaobao * @date 2019/5/11 * @QQ 1527563274 */public class MapTestMain {    public static void main(String[] args){        Map
map = new HashMap<>(); String b = map.put("2","2Value"); String c = map.put("3","3Value"); String a = map.put("1","1Value"); String e = map.put("5","5Value"); String f = map.put("6","6Value"); String d = map.put("4","4Value"); Set
set = map.keySet(); System.out.println("初始"); System.out.println(map.toString()); System.out.println(set.toString()); System.out.println("==============="); System.out.println("set删除1"); set.remove("1"); System.out.println(map.toString()); System.out.println(set.toString()); System.out.println("==============="); System.out.println("map删除3"); map.remove("3"); System.out.println(map.toString()); System.out.println(set.toString()); System.out.println("==============="); }}复制代码

运行后返回结果如下:

我们可以发现不管是对set操作还是对源map进行操作,最后结果都会反映到对方身上。

        那么为什么会这样呢,我们来看一下在HashMap中的KeySet方法:

        我们发现返回的其实是HashMap中的KeySet内部类我们再来看一下HashMap类中KeySet类:

        发现啥没?没错,其实所有的KeySet的操作都是调用HashMap的方法来完成的,这就是为什么对返回的KeySet中的操作会反映到HashMap以及对HashMap进行操作也会反映到KeySet的原因。在我个人认为这也是fail-fast机制出现的根本原因。

fail-fast 实现原理

        我们通过HashMap来讨论fail-fast机制。查看HashMap我们可以发现HashMap中有这么一个变量:

        就是这个modCount协助完成了fail-fast机制。关于modCount我们可以看到注释中是这样写的:记录了这个HashMap结构修改的次数。结构修改指的是更改HashMap中的键值对的数量以及其他方式修改器内部结构(如rehash)的修改。这个字段被用来在HashMap的迭代器上做快速失败。大家可以在HashMap的源码中查看什么时刻modCount被修改了,去验证这一句话。

只有一个modCount还不足以完成fail-fast。我们上文说到,快速失败是在Iterator迭代中产生的。那我们看看在HashMap中内置的Iterator类:

abstract class HashIterator {        Node
next; // next entry to return Node
current; // current entry int expectedModCount; // for fast-fail int index; // current slot HashIterator() { expectedModCount = modCount; Node
[] t = table; current = next = null; index = 0; if (t != null && size > 0) { // advance to first entry do {} while (index < t.length && (next = t[index++]) == null); } } public final boolean hasNext() { return next != null; } final Node
nextNode() { Node
[] t; Node
e = next; if (modCount != expectedModCount) throw new ConcurrentModificationException(); if (e == null) throw new NoSuchElementException(); if ((next = (current = e).next) == null && (t = table) != null) { do {} while (index < t.length && (next = t[index++]) == null); } return e; } public final void remove() { Node
p = current; if (p == null) throw new IllegalStateException(); if (modCount != expectedModCount) throw new ConcurrentModificationException(); current = null; K key = p.key; removeNode(hash(key), key, null, false, false); expectedModCount = modCount; } }复制代码

        没错就是这个抽象类 HashIterator。keySet()、values()、entrySet()方法都是依靠这个类来完成的。现在我们把关注点放在这个抽象类的一个字段上:

        就是红圈圈住的expectedModCount 源码再次很贴心得告诉了我们这个字段用来做fast-fail,那具体这个字段是怎么用的呢? 我们来看HashIterator的构造函数:

        我们发现在Iterator生成的时候这个expectedModCount被赋上了modCount的值。好了,铺垫了这么多,我们终于可以说到重点,那就是fail-fast机制到底是如何实现的,我们把关注点放在HashIterator类中的nextNode()以及remove方法上:

其实现在就很清楚了,当Iterator类内部的expectedModCount!=modCount时就会抛出ConcurrentModificationException。我们现在用代码来演示一下fail-fast:

package com.lichaobao.collectionsown.collections;import java.util.*;import java.util.stream.Stream;/** * @author lichaobao * @date 2019/5/11 * @QQ 1527563274 */public class MapTestMain {    public static void main(String[] args){        Map
map = new HashMap<>(); String b = map.put("2","2Value"); String c = map.put("3","3Value"); String a = map.put("1","1Value"); String e = map.put("5","5Value"); String f = map.put("6","6Value"); String d = map.put("4","4Value"); Set
set = map.keySet(); Iterator
iterable = set.iterator(); System.out.println("初始"); System.out.println(map.toString()); System.out.println(set.toString()); System.out.println("==============="); System.out.println("set删除1"); set.remove("1"); System.out.println(map.toString()); System.out.println(set.toString()); System.out.println("==============="); while (iterable.hasNext()){ System.out.println(iterable.next()); } }}复制代码

        现在我们来看一下运行结果:

        本篇结束!

转载于:https://juejin.im/post/5cdf98c751882525b40cc991

你可能感兴趣的文章
Node.js学习之路27——Express的router对象
查看>>
Druid入门
查看>>
js正则
查看>>
CDN基本工作过程
查看>>
Vue实例
查看>>
红药丸,还是蓝药丸
查看>>
基于 HTML5 WebGL 的 3D 仓储管理系统
查看>>
hadoop集群搭建
查看>>
一步一步创建ASP.NET MVC5程序[Repository+Autofac+Automapper+SqlSugar](五)
查看>>
GoLang 变量作用域
查看>>
聊聊 DisplayObject 的x/y/regX/regY/rotation/scale/skew 属性
查看>>
JavaFX “即时搜索” 示例
查看>>
MongoDB分片+复制集
查看>>
vue 将echarts封装为组件一键使用
查看>>
JS的闭包与this详解
查看>>
从ELK到EFK
查看>>
引入流
查看>>
一次线上游戏卡死的解决历程
查看>>
JavaScript读取剪贴板中的表格生成图片
查看>>
Mac 下终端配置(item2 + oh-my-zsh +3024Night 配色方案)
查看>>