Java - 同步的 ArrayList 仍然是 ConcurrentModificationException

Java - synchronized ArrayList still ConcurrentModificationException

我得到了一个被多次访问的 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 已经同步,它也会在循环中抛出异常。该线程每 class.

只执行一次

有什么想法吗?

有两种可能:

  1. 您在 class 中有 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。它们的唯一关系在于,如果您有一个线程读取列表而另一个线程可能会修改它,则同步会在读取发生时阻止修改。除此之外,它们是无关的。

您在评论中说:

i call a method which removes then

如果要删除循环的条目,可以通过 list iterator's remove 方法进行:

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

(还有addset操作。)

ConcurrentModificationException 在线程的意义上不一定是 'concurrent',它可以是 'concurrent' 在你不应该同时直接修改集合的意义上正在迭代它。

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

Note that this exception does not always indicate that an object has been concurrently modified by a different thread. If a single thread issues a sequence of method invocations that violates the contract of an object, the object may throw this exception. For example, if a thread modifies a collection directly while it is iterating over the collection with a fail-fast iterator, the iterator will throw this exception.

并且 for(x:y) 使用迭代器,它很容易成为 'fail-fast' 迭代器。

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

不能这样做(如果routingTable是同步的)

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