Java-同步的ArrayList仍然ConcurrentModificationException

时间:2019-08-22 07:43:44

标签: java arraylist synchronized

我得到了一个final ArrayList<RoutingTableEntry> routingTable = new ArrayList<>();,可以多次访问。 但是我只能在以下线程中获得ConcurrentModificationException的某个点:

Thread checkReplies = new Thread(() -> {

    while (true) {

        synchronized (routingTable) {

            for (RoutingTableEntry entry : routingTable) { // throws it here

                // do smth
            }
        }

        // [...]
    }
 });
 checkReplies.start();

即使routingTable已经同步,它也会在循环中引发异常。每个类只能执行一次该线程。

有什么想法吗?

3 个答案:

答案 0 :(得分:3)

有两种可能性:

  1. 您的类中有 other 代码,它们可以修改routingTable,并且这样做时不会使用synchronized (routingTable)。因此,当其他代码在该迭代过程中修改列表时,就会出现错误。

  2. 您正在修改列表中包含“ do smth”注释的列表。仅仅因为您已同步了列表,并不意味着您可以在遍历其迭代器时对其进行修改。您不能(除非通过迭代器本身,这意味着您无法使用增强的for循环)。 (有时由于ArrayList实现的细节而无法使用它,但有时却不这样做。)

以下是#2(live copy)的示例:

var routingTable = new ArrayList<String>();
routingTable.add("one");
routingTable.add("two");
routingTable.add("three");
synchronized (routingTable) {
    for (String entry : routingTable) {
        if (entry.equals("two")) {
            routingTable.add("four");
        }
    }
}

JDK12的ArrayList的实现(至少可能是其他实现)失败了。

要理解的一件事是,在迭代过程中同步和修改列表在很大程度上是不相关的概念。同步(正确完成)可防止多个线程同时访问列表。但是,如您在上面的示例中看到的那样,只有一个线程可以通过在迭代期间修改列表来引发ConcurrentModificationException。它们之间的关系仅在于,如果您有一个线程在读取列表,而另一个线程可能在修改列表,则同步会阻止在进行读取时进行修改。除此之外,它们是无关的。

在评论中您说过:

  

我调用了一个方法,然后将其删除

如果要删除循环的条目,则可以通过list iteratorremove方法进行操作:

for (var it = routingTable.listIterator(); it.hasNext; ) {
    var entry = it.next();
    if (/*...some condition...*/) {
        it.remove(); // Removes the current entry
    }
}

(还有addset个操作。)

答案 1 :(得分:2)

ConcurrentModificationException不一定在线程意义上是“并发的”,它可以是“并发”的,因为在迭代它时您不能在同一时间直接修改一个集合。< / p>

很长一段时间也存在于文档中(摘自Java7:https://docs.oracle.com/javase/7/docs/api/java/util/ConcurrentModificationException.html

  

请注意,此异常并不总是表示对象已由其他线程同时修改。如果单个线程发出违反对象约定的方法调用序列,则该对象可能会抛出此异常。 例如,如果线程在使用快速失败迭代器迭代集合时直接修改集合,则迭代器将抛出此异常。

for(x:y)使用一个迭代器,该迭代器很容易最终成为“快速失败”的迭代器。

答案 2 :(得分:0)

从对原始问题的评论开始,您要在routingTable中删除项目,而您要进行迭代

不能做到这一点(即使routingTable已同步)

for (RoutingTableEntry entry : routingTable) { // throws it here
     // routingTable.remove(....);
}