与Enumeration相比,Iterator更加安全,因为当一个集合正在被遍历的时候,它会阻止其它线程去修改集合。
- 我们在做练习的时候,迭代时会不会经常出错,抛出ConcurrentModificationException异常,说我们在遍历的时候还在修改元素。
- 这其实就是fail-fast机制~具体可参考博文:https://blog.csdn.net/panweiwei1994/article/details/77051261
区别有三点:
- Iterator的方法名比Enumeration更科学
- Iterator有fail-fast机制,比Enumeration更安全
- Iterator能够删除元素,Enumeration并不能删除元素
- fail-fast 机制是java容器(Collection和Map都存在fail-fast机制)中的一种错误机制。在遍历一个容器对象时,当容器结构被修改,很有可能会抛出ConcurrentModificationException,产生fail-fast。
-
什么时候会出现fail-fast?
在以下两种情况下会导致fail-fast,抛出ConcurrentModificationException
- 单线程环境
遍历一个集合过程中,集合结构被修改。注意,listIterator.remove()方法修改集合结构不会抛出这个异常。 - 多线程环境
当一个线程遍历集合过程中,而另一个线程对集合结构进行了修改。 -
单线程环境例子
import java.util.ListIterator; import java.util.Vector; public class Test { /** * 单线程测试 */ @org.junit.Test public void test() { try { // 测试迭代器的remove方法修改集合结构会不会触发checkForComodification异常 ItrRemoveTest(); System.out.println("----分割线----"); // 测试集合的remove方法修改集合结构会不会触发checkForComodification异常 ListRemoveTest(); } catch (Exception e) { e.printStackTrace(); } } // 测试迭代器的remove方法修改集合结构会不会触发checkForComodification异常 private void ItrRemoveTest() { Vector list = new Vector<>(); list.add("1"); list.add("2"); list.add("3"); ListIterator itr = list.listIterator(); while (itr.hasNext()) { System.out.println(itr.next()); //迭代器的remove方法修改集合结构 itr.remove(); } } // 测试集合的remove方法修改集合结构会不会触发checkForComodification异常 private void ListRemoveTest() { Vector list = new Vector<>(); list.add("1"); list.add("2"); list.add("3"); ListIterator itr = list.listIterator(); while (itr.hasNext()) { System.out.println(itr.next()); //集合的remove方法修改集合结构 list.remove("3"); } } }
-
运行结果
1 2 3 ----分割线---- 1 java.util.ConcurrentModificationException at java.util.Vector$Itr.checkForComodification(Unknown Source)
-
从结果中可以看到迭代器itr的remove操作并没有出现ConcurrentModificationException异常。而集合的remove操作则产生了异常。
-
fail-fast实现原理
- 下面是Vector中迭代器Itr的部分源码
-
/** * An optimized version of AbstractList.Itr */ private class Itr implements Iterator<E> { int expectedModCount = modCount; //省略的部分代码 public void remove() { if (lastRet == -1) throw new IllegalStateException(); synchronized (Vector.this) { checkForComodification(); Vector.this.remove(lastRet); expectedModCount = modCount; } cursor = lastRet; lastRet = -1; } @Override public void forEachRemaining(Consumer<? super E> action) { //省略的部分代码 checkForComodification(); } } final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
-
从代码中可以看到,每次初始化一个迭代器都会执行
int expectedModCount = modCount;
。modcount意为moderate count,即修改次数,对集合内容的修改都将增大这个值,如modCount++;
。在迭代器初始化过程中会执行int expectedModCount = modCount;
来记录迭会通过checkForComodification()方法判断modCount和expectedModCount 是否相等,如果不相等就表示已经有线程修改了集合结构。 - 使用迭代器的remove()方法修改集合结构不会触发ConcurrentModificationException,现在可以在源码中看出来是为什么。在remove()方法的最后会执行
expectedModCount = modCount;
,这样itr.remove操作后modCount和expectedModCount依然相等,就不会触发ConcurrentModificationException了。 -
如何避免fail-fast?
使用java.util.concurrent包下的类去取代java.util包下的类。所以,本例中只需要将Vector替换成java.util.concurrent包下对应的类即可。
- 原因:并发机制导致每次迭代过程中发生改变时,下一次的迭代得到得是最新的数据结构,例如corrunenthashmap
- (参考:https://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/index.html)。
-
- 单线程环境