下一次循环执行到 String str = (String) var3.next(); 的时候,就会调用 checkForComodification 方法,此时一个为 3,一个为 4,就只好抛出异常 ConcurrentModificationException 了。
不信,可以直接在 ArrayList 类的 909 行打个断点 debug 一下。
真的耶,一个是 4 一个是 3。
总结一下。在 for each 循环中,集合遍历其实是通过迭代器 Iterator 配合 while 循环实现的,但是元素的 remove 却直接使用的集合类自身的方法。这就导致 Iterator 在遍历的时候,会发现元素在自己不知情的情况下被修改了,它觉得很难接受,就抛出了异常。
读者朋友们,你们是不是觉得我跑题了,fail-fast 和 for each 中集合的 remove 操作有什么关系呢?
有!Iterator 使用了 fail-fast 的保护机制。
04、怎么避开 fail-fast 保护机制呢通过上面的分析,相信大家都明白为什么不能在 for each 循环里进行元素的 remove 了。
那怎么避开 fail-fast 保护机制呢?毕竟删除元素是常规操作,咱不能因噎废食啊。
1)remove 后 break
List<String> list = new ArrayList<>(); list.add("沉默王二"); list.add("沉默王三"); list.add("一个文章真特么有趣的程序员"); for (String str : list) { if ("沉默王二".equals(str)) { list.remove(str); break; } }我怎么这么聪明,忍不住骄傲一下。有读者不明白为什么吗?那我上面的源码分析可就白分析了,爬楼再看一遍吧!
略微透露一下原因:break 后循环就不再遍历了,意味着 Iterator 的 next 方法不再执行了,也就意味着 checkForComodification 方法不再执行了,所以异常也就不会抛出了。
但是呢,当 List 中有重复元素要删除的时候,break 就不合适了。
2)for 循环
List<String> list = new ArrayList<>(); list.add("沉默王二"); list.add("沉默王三"); list.add("一个文章真特么有趣的程序员"); for (int i = 0, n = list.size(); i < n; i++) { String str = list.get(i); if ("沉默王二".equals(str)) { list.remove(str); } }for 循环虽然可以避开 fail-fast 保护机制,也就说 remove 元素后不再抛出异常;但是呢,这段程序在原则上是有问题的。为什么呢?
第一次循环的时候,i 为 0,list.size() 为 3,当执行完 remove 方法后,i 为 1,list.size() 却变成了 2,因为 list 的大小在 remove 后发生了变化,也就意味着“沉默王三”这个元素被跳过了。能明白吗?
remove 之前 list.get(1) 为“沉默王三”;但 remove 之后 list.get(1) 变成了“一个文章真特么有趣的程序员”,而 list.get(0) 变成了“沉默王三”。
3)Iterator
List<String> list = new ArrayList<>(); list.add("沉默王二"); list.add("沉默王三"); list.add("一个文章真特么有趣的程序员"); Iterator<String> itr = list.iterator(); while (itr.hasNext()) { String str = itr.next(); if ("沉默王二".equals(str)) { itr.remove(); } }为什么使用 Iterator 的 remove 方法就可以避开 fail-fast 保护机制呢?看一下 remove 的源码就明白了。
public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }虽然删除元素依然使用的是 ArrayList 的 remove 方法,但是删除完会执行 expectedModCount = modCount,保证了 expectedModCount 与 modCount 的同步。
05、最后在 Java 中,fail-fast 从狭义上讲是针对多线程情况下的集合迭代器而言的。这一点可以从 ConcurrentModificationException 定义上看得出来。
This exception may be thrown by methods that have detected concurrent
modification of an object when such modification is not permissible.
For example, it is not generally permissible for one thread to modify a Collectionwhile another thread is iterating over it. In general, the results of theiteration are undefined under these circumstances. Some Iteratorimplementations (including those of all the general purpose collection implementationsprovided by the JRE) may choose to throw this exception if this behavior isdetected. Iterators that do this are known as fail-fast iterators,as they fail quickly and cleanly, rather that risking arbitrary,non-deterministic behavior at an undetermined time in the future.
再次拙劣地翻译一下。
该异常可能由于检测到对象在并发情况下被修改而抛出的,而这种修改是不允许的。