12

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是线程安全的。

编辑:使用 Iterator 时,它会抛出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);
    }
}

请检查。

4

7 回答 7

12

请注意,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,您就不需要副本。

于 2012-06-04T11:28:56.093 回答
4

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

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

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

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

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

于 2012-06-04T11:16:50.677 回答
3

正如您在http://docs.oracle.com/javase/6/docs/api/java/util/Vector.html中阅读的那样,仅针对迭代器实现了引发 ConcurrentModificationException 的快速失败行为

于 2012-06-04T11:11:31.273 回答
3

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

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

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

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

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

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

于 2012-06-04T11:30:49.923 回答
1

简短的回答:它bonus feature是在枚举已经存在之后发明的,所以枚举器不抛出它的事实并不表明有什么特别的。

长答案:
来自维基百科:

JDK 1.2 之前的集合实现 [...] 不包含集合框架。对 Java 对象进行分组的标准方法是通过数组、Vector 和 Hashtable 类,遗憾的是它们不容易扩展,并且没有实现标准的成员接口。为了满足对可重用集合数据结构的需求 [...] 集合框架主要由 Joshua Bloch 设计和开发,并在 JDK 1.2 中引入。

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

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

于 2012-06-04T11:40:29.477 回答
0

这取决于您如何获得枚举。请参阅以下示例,它确实抛出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);
        }       
    }  
于 2012-06-04T14:53:33.060 回答
-1

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

编辑:对不起,误读了那里的问题

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

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

于 2012-06-04T11:00:43.000 回答