fail-fast和fail-safe的概念
我们通常说的Java中的fail-fast机制,默认的话指的是Java集合的一种错误检测机制。当多个线程对部分集合进行结构上的改变的操作时,有可能会产生fail-fast机制,这个时候就会抛出ConcurrentModificationException。
ConcurrentModificationException:当方法检测到对象的并发修改,但不允许这种修改时就会抛出该异常信息。
在Java中,如果在foreach循环里对某些集合元素进行元素的remove/add操作的时候,就会触发fail-fast机制,进而抛出ConcurrentModificationException。
如以下代码:
List<String> userNames = new ArrayList<String>() {{
add("yang");
add("xiao");
add("yuan");
add("Y");
}};
for(String userName : userNames) {
if(userName.equals("yang")) {
userNames.remove(userName);
}
}
System.out.println(userNames);
以上代码,使用增强版for循环遍历集合元素,并尝试删除其中的yang字符串元素。运行这段代码,就会抛出异常。如图:
同样的,大家可以尝试一下在增强for循环中使用add方法添加元素,结果会抛出同样的异常信息。
我们在做深入理解之前,先尝试把foreach进行解语法糖,看一下foreach具体是怎么实现的。
我们使用jad工具,对编译后的class文件进行反编译,得到如下代码:
public static void main(String args[]) {
//说明:使用ImmutableList初始化一个List
List<String> userNames = new ArrayList<String>() {{
add("yang");
add("xiao");
add("yuan");
add("Y");
}};
Iterator iterator = new userNames.iterator();
//do……while
do
{
if(!iterator.hashNext())
break;
//这里做一下显示转换
String userName = (String)iterator.next();
if(userName.equals("yang"))
userNames.remove(userName);
}while(true);
System.out.println(userNames);
}
可以发现,foreach其实是依赖了while循环和Iterator实现的。
通过以上代码的异常堆栈,我们可以跟踪到真正抛出异常的代码是哪里:
该方法是在iterator.next()方法中调用的,我们看下这个方法的实现:
final void checkForComodification() {
if(modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
正如上面,在该方法中对modCount 和 expectedModCount进行了比较,如果二者不相等,则抛出ConcurrentModificationException。
那么,modCount 和 expectedModCount是什么呢?是什么原因导致他们的值不相等呢?
带着问题找答案!modCount是ArrayList中的一个成员变量。它本身表示该集合实际被修改的次数。
List<String> userNames = new ArrayList<String>() {{
add("yang");
add("xiao");
add("yuan");
add("Y");
}};
当使用以上代码初始化集合之后该变量是不是就没有了,没有了初始值就是0。
expectedModCount是ArrayList中的一个内部类 ———Itr中的成员变量。
Iterator iterator = new userNames.iterator();
以上代码,即可得到一个Itr类,该类实现了Iterator接口。
expectedModCount表示这个迭代器预期该集合被修改的次数,他的值随着Itr被创建而初始化。只有通过迭代器对集合进行操作,这个值才会被改变。(重点)
那么,接下来看一下userNames.remove(userName);方法里面做了什么事情,为什么导致expectedModCount和modCount的值不一样。
通过我不断地翻阅源码,发现,remove方法核心逻辑,代码如下:
private void fastRemove(int index) {
modCount++;
int numMoved = size - index -1;
if(numMoved >0) {
System.arraycopy(elementData,index+1,elementData,index,numMoved);
elementData[--size] = null;//clear to let GC do its work
}
}
可以看到,它只修改了modCount,并没有对expectedModCount做任何操作。
要是感觉还是很难理解的话,那我就大家简单画一个图吧。
简单总结一下吧!之所以会抛出CMException异常,是因为我们的代码中使用了增强办的for循环,而在增强版的for循环中,集合遍历是通过iterator进行的,但是元素的add/remove确是直接使用的集合类自己的方法。这就导致iterator在遍历的时候,会发现有一个元素在自己不知不觉的情况下就被add/remove掉了,就会抛出一个异常,用来提示用户,可能发生了并发修改!
所以呢,在使用Java的集合类的时候,如果发生CMException,优先考虑fail-fast机制的有关的情况,实际上这里并没有真正的发生并发,只是Iterator使用了fail-fast的保护机制,就是只要它发现有某一次修改是未经过自己进行的,那他就会吃醋,抛出异常。
Ok,这是对集合类中的fail-fast的详细说明,下一篇博文是fail-safe的详细说明,记得订阅哦!