GitHub 2.1k Star 的 Java工程师成神之路 ,不来了解一下吗?
GitHub 2.1k Star 的 Java工程师成神之路 ,真的不来了解一下吗?
GitHub 2.1k Star 的 Java工程师成神之路 ,真的确定不来了解一下吗?
我在《为什么阿里巴巴禁止在 foreach 循环里进行元素的 remove/add 操作》一文中曾经介绍过Java中的fail-fast机制,但是并没有深入介绍,本文,就来深入介绍一下fail-fast。
什么是fail-fast
首先我们看下维基百科中关于fail-fast的解释:
In systems design, a fail-fast system is one which immediately reports at its interface any condition that is likely to indicate a failure. Fail-fast systems are usually designed to stop normal operation rather than attempt to continue a possibly flawed process. Such designs often check the system's state at several points in an operation, so any failures can be detected early. The responsibility of a fail-fast module is detecting errors, then letting the next-highest level of the system handle them.
大概意思是:在系统设计中,快速失效系统一种可以立即报告任何可能表明故障的情况的系统。快速失效系统通常设计用于停止正常操作,而不是试图继续可能存在缺陷的过程。这种设计通常会在操作中的多个点检查系统的状态,因此可以及早检测到任何故障。快速失败模块的职责是检测错误,然后让系统的下一个最高级别处理错误。
其实,这是一种理念,说白了就是在做系统设计的时候先考虑异常情况,一旦发生异常,直接停止并上报。
举一个最简单的fail-fast的例子:
public int divide(int divisor,int dividend){
if(dividend == 0){
throw new RuntimeException("dividend can't be null");
}
return divisor/dividend;
}
复制代码
上面的代码是一个对两个整数做除法的方法,在divide方法中,我们对被除数做了个简单的检查,如果其值为0,那么就直接抛出一个异常,并明确提示异常原因。这其实就是fail-fast理念的实际应用。
这样做的好处就是可以预先识别出一些错误情况,一方面可以避免执行复杂的其他代码,另外一方面,这种异常情况被识别之后也可以针对性的做一些单独处理。
怎么样,现在你知道fail-fast了吧,其实他并不神秘,你日常的代码中可能经常会在使用的。
既然,fail-fast是一种比较好的机制,为什么文章标题说fail-fast会有坑呢?
原因是Java的集合类中运用了fail-fast机制进行设计,一旦使用不当,触发fail-fast机制设计的代码,就会发生非预期情况。
集合类中的fail-fast
我们通常说的Java中的fail-fast机制,默认指的是Java集合的一种错误检测机制。当多个线程对部分集合进行结构上的改变的操作时,有可能会产生fail-fast机制,这个时候就会抛出ConcurrentModificationException(后文用CME代替)。
CMException,当方法检测到对象的并发修改,但不允许这种修改时就抛出该异常。
很多时候正是因为代码中抛出了CMException,很多程序员就会很困惑,明明自己的代码并没有在多线程环境中执行,为什么会抛出这种并发有关的异常呢?这种情况在什么情况下才会抛出呢?我们就来深入分析一下。
异常复现
在Java中, 如果在foreach 循环里对某些集合元素进行元素的 remove/add 操作的时候,就会触发fail-fast机制,进而抛出CMException。
如以下代码:
List<String> userNames = new ArrayList<String>() {{
add("Hollis");
add("hollis");
add("HollisChuang");
add("H");
}};
for (String userName : userNames) {
if (userName.equals("Hollis")) {
userNames.remove(userName);
}
}
System.out.println(userNames);
复制代码
以上代码,使用增强for循环遍历元素,并尝试删除其中的Hollis字符串元素。运行以上代码,会抛出以下异常:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at com.hollis.ForEach.main(ForEach.java:22)
复制代码
同样的,读者可以尝试下在增强for循环中使用add方法添加元素,结果也会同样抛出该异常。
在深入原理之前,我们先尝试把foreach进行解语法糖,看一下foreach具体如何实现的。
我们使用 jad 工具,对编译后的class进行反编译,得到以下代码:
public static void main(String[] args) {
// 使用ImmutableList初始化一个List
List<String> userNames = new ArrayList<String>() {{
add("Hollis");
add("hollis");
add("HollisChuang");
add("H");
}};
Iterator iterator = userNames.iterator();
do
{
if(!iterator.hasNext())
break;
String userName = (String)iterator.next();
if(userName.equals("Hollis"))
userNames.remove(userName);
} while(true);
System.out.println(userNames);
}
复制代码
可以发现,foreach其实是依赖了while循环和Iterator实现的。
异常原理
通过以上代码的异常堆栈,我们可以跟踪到真正抛出异常的代码是: