从 modCount 看 java集合 fail-fast 机制
一、背景
在常见的java的非线程安全集合类中(如hashmap、arraylist),经常可以在一些修改结构的操作(如add)中看到实例变量 modcount++
,来统计集合的修改次数。
从注释也可以看出,该字段是为 fail-fast(快速失败)机制服务。
二、简介
fail-fast 机制是能立刻报告任何可能导致失败的错误检测机制。
在java集合框架中表现为:当构建迭代器时,起初expectedmodcount = modcount
,当修改了该集合时,则该集合modcount++
,随后迭代器在迭代下个元素时,会发现当前的modcount
值与期望值expectedmodcount
不符,便会抛出concurrentmodificationexception
异常,以避免数据不同步带来的麻烦。
注:
- 使用迭代器的remove()方法不会抛出异常,看源码可知在其中重新设置expectedmodcount。
-
该机制不保证存在非同步并发修改时一定会抛出异常,只是尽最大努力去抛出该异常,因此最好不要依赖于该异常去写程序,而只是用于debug阶段。
官方注释:
note that fail-fast behavior cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. fail-fast operations throw concurrentmodificationexception on a best-effort basis. therefore, it would be wrong to write a program that depended on this exception for its correctness: concurrentmodificationexception should be used only to detect bugs.
三、两种出现场景
3.1 单线程环境
在遍历集合的过程中,调用了集合修改方法。
例:
class test{ public static void main(string[] args){ arraylist<integer> list = new arraylist(); list.add(1); list.add(2); list.add(3); iterator itr = list.iterator(); while(itr.hasnext()){ system.out.println(itr.next());// 1 \n 2 \n 3 itr.remove(); //后续不会抛出异常 } system.out.println(list.tostring());// 输出:[] list.add(1); list.add(2); list.add(3); itr = list.iterator(); while(itr.hasnext()){ object i = itr.next(); system.out.println(i); list.remove(i); //后续next时会抛出异常 } } }
3.2 多线程环境
一个线程在遍历该集合时,另一个线程修改了该集合结构。
arraylist<integer> list = new arraylist(); list.add(1); list.add(2); list.add(3); for(integer i: list){ new thread(() -> { list.add(4); }).run(); system.out.println(i); }
四、迭代器源码解析
以arraylist对iterator的实现为例:
private class itr implements iterator<e> { int cursor; // index of next element to return int lastret = -1; // index of last element returned; -1 if no such int expectedmodcount = modcount; // 期望的modcount值即为创建迭代器时的modcount值 itr() {} public boolean hasnext() { // hasnext 并不抛异常 return cursor != size; } @suppresswarnings("unchecked") public e next() { checkforcomodification(); //首先检查expectedmodcount是否一致 // …省略 } public void remove() { if (lastret < 0) throw new illegalstateexception(); checkforcomodification(); try { arraylist.this.remove(lastret); cursor = lastret; lastret = -1; expectedmodcount = modcount; //迭代器remove后不抛异常的原因,更新 expectedmodcount } catch (indexoutofboundsexception ex) { throw new concurrentmodificationexception(); } } /** 检查 expectedmodcount 与 当前 modcount是否一致,否则抛异常*/ final void checkforcomodification() { if (modcount != expectedmodcount) throw new concurrentmodificationexception(); } }
参考: