与Enumeration相比,Iterator更加安全,因为当一个集合正在被遍历的时候,它会阻止其它线程去修改集合

区别有三点:

  • 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)。
posted on 2018-07-19 14:42  千重峰  阅读(174)  评论(0编辑  收藏  举报