3

ConcurrentModificationException :当这种修改是不允许的时,检测到对象的并发修改的方法可能会抛出此异常。

以上是来自 javadoc 的 ConcurrentModificationException 定义。

所以我尝试测试下面的代码:

final List<String> tickets = new ArrayList<String>(100000);
for (int i = 0; i < 100000; i++) {
    tickets.add("ticket NO," + i);
}
for (int i = 0; i < 10; i++) {
    Thread salethread = new Thread() {
        public void run() {
            while (tickets.size() > 0) {
                tickets.remove(0);
                System.out.println(Thread.currentThread().getId()+"Remove 0");
            }
        }
    };
    salethread.start();
}

代码很简单。10 个线程从 arraylist 对象中删除元素。可以确定多个线程访问一个对象。但它运行正常。不会抛出异常。为什么?

4

5 回答 5

7

为了您的利益,我引用了ArrayListJavadoc 的大部分内容。突出显示解释您所看到的行为的相关部分。

请注意,此实现不同步。如果多个线程同时访问一个 ArrayList 实例,并且至少有一个线程在结构上修改了列表,则必须在外部进行同步。(结构修改是添加或删除一个或多个元素,或显式调整后备数组大小的任何操作;仅设置元素的值不是结构修改。)这通常通过同步一些自然封装的对象来完成列表。如果不存在这样的对象,则应使用 Collections.synchronizedList 方法“包装”该列表。这最好在创建时完成,以防止对列表的意外不同步访问:

列表列表 = Collections.synchronizedList(new ArrayList(...));

此类的 iterator 和 listIterator 方法返回的迭代器是快速失败的:如果在创建迭代器后的任何时间对列表进行结构修改,除了通过迭代器自己的 remove 或 add 方法之外的任何方式,迭代器将抛出 ConcurrentModificationException。因此,面对并发修改,迭代器快速而干净地失败,而不是在未来不确定的时间冒任意的、非确定性的行为。

请注意,不能保证迭代器的快速失败行为,因为一般来说,在存在不同步的并发修改的情况下,不可能做出任何硬保证。快速失败的迭代器会尽最大努力抛出 ConcurrentModificationException。因此,编写一个依赖于这个异常的正确性的程序是错误的:迭代器的快速失败行为应该只用于检测错误。

如果您在通过其迭代器访问列表时对列表进行结构性修改,ArrayLists 通常会引发并发修改异常(但这也不是绝对的保证)。请注意,在您的示例中,您直接从列表中删除元素,并且您没有使用迭代器。

如果您喜欢它,您还可以浏览 的实现ArrayList.remove,以更好地了解它的工作原理。

于 2013-02-19T02:50:52.357 回答
2

在这种情况下,我认为“并发”并不意味着与线程相关,或者至少并不一定意味着那样。ConcurrentModificationExceptions 通常源于在迭代过程中修改集合。

List<String> list = new ArrayList<String>();
for(String s : list)
{
     //modifying list results in ConcurrentModificationException
     list.add("don't do this");     

}

请注意,Iterator<>该类有一些方法可以规避这一点:

for(Iterator it = list.iterator(); it.hasNext())
{
     //no ConcurrentModificationException
     it.remove(); 
}
于 2013-02-19T02:53:57.347 回答
1

您没有收到 a 的原因是ConcurrentModificationException没有ArrayList.remove抛出一个。您可能可以通过启动一个遍历数组的附加线程来获得一个:

final List<String> tickets = new ArrayList<String>(100000);
for (int i = 0; i < 100000; i++) {
    tickets.add("ticket NO," + i);
}
for (int i = 0; i < 10; i++) {
    Thread salethread = new Thread() {
        public void run() {
            while (tickets.size() > 0) {
                tickets.remove(0);
                System.out.println(Thread.currentThread().getId()+"Remove 0");
            }
        }
    };
    salethread.start();
}
new Thread() {
    public void run() {
        int totalLength = 0;
        for (String s : tickets) {
            totalLength += s.length();
        }
    }
}.start();
于 2013-02-19T02:53:17.000 回答
1

因为您没有使用迭代器,所以没有机会ConcurrentModificationException被抛出。

调用remove(0)只会删除第一个元素。如果另一个线程在执行完成之前删除 0,它可能不是调用者想要的相同元素。

于 2013-02-19T02:59:06.010 回答
1

但它运行正常。不会抛出异常。为什么?

仅仅是因为允许同时进行修改。

异常的描述是这样说的:

“当这种修改是不允许的时,检测到对象的并发修改的方法可能会抛出此异常。 ”

明确的暗示是(或可能是)允许的并发修改。事实上,对于标准的 Java 非并发集合类,并发修改是允许的……前提是它们不会在迭代期间发生。


这背后的原因是,对于非并发集合,在迭代时修改从根本上是不安全和不可预测的。即使您要正确同步(这并不容易1),结果仍然是不可预测的。并发修改的“快速失败”检查包含在常规集合类中,因为这是使用 Java 1.1 集合类的多线程应用程序中 Heisenbugs 的常见来源。

1- 例如,“synchronizedXxx”包装类不会,也不能与迭代器同步。问题是迭代涉及对 and 的交替调用next()hasNext()并且在排除其他线程的同时进行一对方法调用的唯一方法是使用外部同步。包装器方法在 Java 中不实用。

于 2013-02-19T03:26:21.047 回答