【问题标题】:Java Enumeration vs IteratorJava 枚举与迭代器
【发布时间】:2012-06-08 10:58:27
【问题描述】:

Enumeration 不会抛出 ConcurrentModificationException ,为什么?

见下面的代码。

public static void main(String[] args) {

    Vector<String> v=new Vector<String>();
    v.add("Amit");
    v.add("Raj");
    v.add("Pathak");
    v.add("Sumit");
    v.add("Aron");
    v.add("Trek");

    Enumeration<String> en=v.elements();

    while(en.hasMoreElements())
    {
        String value=(String) en.nextElement();
        System.out.println(value);
        v.remove(value);

    }

}

它只打印:

阿米特 帕塔克 阿隆

为什么会出现这种行为。我们可以说Enumerator 是线程安全的吗?

编辑:使用迭代器时,它会在单线程应用程序中抛出 ConcurrentModificationException

public static void main(String[] args) {

    Vector<String> v=new Vector<String>();
    v.add("Amit");
    v.add("Raj");
    v.add("Pathak");
    v.add("Sumit");
    v.add("Aron");
    v.add("Trek");

    Iterator<String> it=v.iterator();
    while(it.hasNext())
    {
        String value=(String) it.next();
        System.out.println(value);
        v.remove(value);
    }
}

请检查。

【问题讨论】:

标签: java


【解决方案1】:

删除v.remove(value),一切都会按预期进行

编辑:抱歉,这里的问题看错了

这与线程安全无关。你甚至不是多线程,所以 Java 没有理由为此抛出异常。

如果您在更改矢量时需要例外,请使其不可修改

【讨论】:

  • 这似乎不是问题的答案
【解决方案2】:

抛出 ConcurrentModificationException 的快速失败行为仅针对 Iterator 实现,您可以阅读 http://docs.oracle.com/javase/6/docs/api/java/util/Vector.html

【讨论】:

    【解决方案3】:

    枚举不会抛出 ConcurrentModificationException ,为什么?

    因为在被调用的代码中没有引发此异常的路径。 编辑:我指的是 Vector 类提供的实现,而不是一般的 Enumeration 接口。

    为什么会出现这种行为。我们能说 Enumerator 是线程安全的吗?

    从某种意义上说,执行的代码是正确同步的,它是线程安全的。但是,我不认为你的循环产生的结果是你想要的。

    您输出的原因是 Enumeration 对象维护一个计数器,该计数器在每次调用 nextElement() 后递增。此计数器不知道您调用了remove()

    【讨论】:

    • 我的理解是枚举和迭代器一样引用集合。一旦我们从向量中删除元素,它就会在 Enumeration 中注意到。那么如果我们先打印然后删除,为什么它打印 3 而不是全部 5。
    • 这就是重点。枚举不会注意到您对 remove 的调用(这将使它需要减少它的索引变量)。
    【解决方案4】:

    请注意,ConcurrentModificationException 与多线程或线程安全意义上的并发无关。有些集合允许并发修改,有些则不允许。通常你可以在文档中找到答案。但是并发并不意味着由不同的线程并发。这意味着您可以在迭代时修改集合。

    ConcurrentHashMap 是一种特殊情况,因为它被明确定义为线程安全且在迭代时可编辑(我认为所有线程安全集合都是如此)。

    无论如何,只要您使用单线程来迭代和修改集合,ConcurrentHashMap 是您问题的错误解决方案。您错误地使用了 API。您应该使用Iterator.remove() 删除项目。或者,您可以在迭代和修改原始集合之前制作集合的副本。

    编辑:

    我不知道有任何枚举会引发 ConcurrentModificationException。但是,并发修改的行为可能不是您所期望的。正如您在示例中看到的,枚举跳过列表中的每个第二个元素。这是因为无论删除如何,它的内部索引都会增加。所以这就是发生的事情:

    • en.nextElement() - 从 Vector 返回第一个元素,将 index 增加到 1
    • v.remove(value) - 从 Vector 中删除第一个元素,将所有元素向左移动
    • en.nextElement() - 从 Vector 返回第二个元素,现在是“Pathak”

    Iterator 的快速失败行为可以保护您免受此类事情的影响,这就是为什么它通常比 Enumberation 更可取。相反,您应该执行以下操作:

    Iterator<String> it=v.iterator();
    while(it.hasNext())
    {
        String value=(String) it.next();
        System.out.println(value);
        it.remove(); // not v.remove(value); !!
    }
    

    或者:

    for(String value : new Vector<String>(v)) // make a copy
    {
        String value=(String) it.next();
        System.out.println(value);
        v.remove(value);
    }
    

    第一个当然更可取,因为只要您按预期使用 API,您就不需要副本。

    【讨论】:

    • 我终于明白枚举永远不会抛出 ConcurrentModificationException?
    • +1 表示“ConcurrentModificationException 与多线程或线程安全意义上的并发无关”
    【解决方案5】:

    这里的并发修改与线程无关。

    这里的并发仅仅意味着你正在修改集合你正在迭代它。 (在您的示例中,这发生在同一个线程中。)

    在这种情况下,集合的迭代器和枚举可以抛出ConcurrentModificationException,但不是必须的。那些确实表现出 fail-fast 行为的行为。显然,Vector 的枚举并不是快速失败的。

    线程安全显然以某种方式涉及多个线程。 Vector 仅在其操作(如添加、获取等)是同步的意义上是线程安全的。这是为了避免当一个线程添加一个元素而同时另一个线程试图删除一个元素时出现不确定的行为。

    现在,当一个线程在结构上修改您的集合,而另一个线程正在对其进行迭代时,您必须同时处理线程安全和并发修改问题。在这种情况下,可能一般来说,最安全的做法是不要依赖 ConcurrentModificationException 被抛出。最好选择合适的集合实现(例如线程安全的实现)并自己避免/禁止并发修改。

    一些迭代器允许通过迭代器本身添加/设置/删除元素。如果您确实需要并发修改,这可能是一个不错的选择。

    【讨论】:

      【解决方案6】:

      简短的回答:它是在枚举已经存在之后发明的bonus feature,因此枚举器不抛出它的事实并不表明有任何特别之处。

      长答案:
      来自维基百科:

      JDK 1.2 之前的集合实现 [...] 不包含 集合框架。 Java 分组的标准方法 对象是通过数组、Vector 和 Hashtable 类, 不幸的是,它不容易扩展,也没有实现 标准会员界面。解决可重复使用的需求 集合数据结构 [...] 集合框架主要由 Joshua 设计和开发 Bloch,并在 JDK 1.2 中引入。

      当 Bloch 团队这样做时,他们认为引入一种新机制是一个好主意,当他们的集合在多线程程序中未正确同步时会发出警报 (ConcurrentModificationException)。关于这个机制有两点需要注意:1) 不能保证捕获并发错误——只有在幸运的情况下才会抛出异常。 2)如果您使用单个线程(如您的示例)滥用集合,也会引发异常。

      因此,一个集合在被多个线程访问时不抛出 ConcurrentModificationException 并不意味着它是线程安全的。

      【讨论】:

        【解决方案7】:

        这取决于您如何获得枚举。看下面的例子,它确实抛出了ConcurrentModificationException

        import java.util.*;
        
        public class ConcurrencyTest {  
            public static void main(String[] args) {  
        
                Vector<String> v=new Vector<String>();
                v.add("Amit");
                v.add("Raj");
                v.add("Pathak");
                v.add("Sumit");
                v.add("Aron");
                v.add("Trek");
        
                Enumeration<String> en=Collections.enumeration(v);//v.elements(); 
        
                while(en.hasMoreElements())
                {
                    String value=(String) en.nextElement();
                    System.out.println(value);
                    v.remove(value);
                }              
        
                System.out.println("************************************");
        
                Iterator<String> iter = v.iterator();  
                   while(iter.hasNext()){  
                      System.out.println(iter.next());  
                      iter.remove();  
                      System.out.println(v.size());  
                }  
            }  
        }  
        

        枚举只是一个接口,它的实际行为是依赖于实现的。 Collections.enumeration() 调用中的 Enumeration 以某种方式包装了迭代器,因此它确实是快速失败的,但通过调用 Vector.elements() 获得的 Enumeration 不是。

        not-fail-fast Enumeration 可能会在未来不确定的时间引入任意的、非确定性的行为。例如:如果你把main方法写成这样,它会在第一次迭代后抛出java.util.NoSuchElementException。

        public static void main(String[] args) {  
        
                Vector<String> v=new Vector<String>();
                v.add("Amit");
                v.add("Raj");
                v.add("Pathak");
        
                Enumeration<String> en = v.elements(); //Collections.enumeration(v);
        
                while(en.hasMoreElements())
                {   
                    v.remove(0);
                    String value=(String) en.nextElement();
                    System.out.println(value);
                }       
            }  
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-12-14
          • 2010-10-31
          • 2014-06-09
          • 2011-02-07
          • 1970-01-01
          • 1970-01-01
          • 2014-10-28
          相关资源
          最近更新 更多