简体   繁体   English

Java-同步的ArrayList仍然ConcurrentModificationException

[英]Java - synchronized ArrayList still ConcurrentModificationException

I got an final ArrayList<RoutingTableEntry> routingTable = new ArrayList<>(); 我得到了final ArrayList<RoutingTableEntry> routingTable = new ArrayList<>(); which is accessed multiple times. 可以多次访问。 But I only get at one Point an ConcurrentModificationException which is in the following Thread: 但是我只能在以下线程中获得一个ConcurrentModificationException

Thread checkReplies = new Thread(() -> {

    while (true) {

        synchronized (routingTable) {

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

                // do smth
            }
        }

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

It throws the exception at the loop even though the routingTable is already synchronized. 即使routingTable已经同步,它也会在循环处引发异常。 This thread gets only executed once per class. 每个类仅执行一次该线程。

Any ideas? 有任何想法吗?

There are two possibilities: 有两种可能性:

  1. You have other code in the class that modifies routingTable , and doesn't use synchronized (routingTable) when doing so. 您的类中还有其他代码可修改routingTable ,并且这样做时不使用synchronized (routingTable) So when the other code modifies the list during that iteration, you get the error. 因此,当其他代码在该迭代期间修改列表时,您会得到错误。

  2. You're modifying the list where you have the comment "do smth". 您正在修改具有注释“ do smth”的列表。 Just because you have have the list synchronized, that doesn't mean you can modify it while looping through with its iterator. 仅仅因为您已同步了列表,并不意味着您可以在遍历其迭代器时对其进行修改。 You can't (except through the iterator itself, which would mean you couldn't use the enhanced for loop). 您不能(除非通过迭代器本身,这意味着您不能使用增强的for循环)。 (Sometimes you get away with it because of the details of the ArrayList implementation, but other times you don't.) (有时由于ArrayList实现的细节而无法使用它,但有时却不这样做。)

Here's an example of #2 ( live copy ): 这是#2( 实时复制 )的示例:

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");
        }
    }
}

That fails with JDK12's implementation of ArrayList (at least, probably others). JDK12的ArrayList实现(至少可能是其他实现)失败了。

One key thing to understand is that synchronization and modifying the list during iteration are largely unrelated concepts. 要理解的一件事是,在迭代过程中同步和修改列表在很大程度上是不相关的概念。 Synchronization (done properly) prevents multiple threads from accessing the list at the same time. 同步(正确完成)可防止多个线程同时访问列表。 But as you can see in the example above, just a single thread can cause a ConcurrentModificationException by modifying the list during the iteration. 但是,如您在上面的示例中看到的那样,只有一个线程可以通过在迭代期间修改列表来引发ConcurrentModificationException。 They only relate in that if you have one thread reading the list and another thread that may modify it, synchronization prevents the modification while the read is happening. 它们之间的关系仅在于,如果您有一个线程在读取列表,而另一个线程可能在修改列表,则同步会阻止在进行读取时进行修改。 Other than that, they're unrelated. 除此之外,它们是无关的。

In a comment you've said: 在评论中,您说过:

i call a method which removes then 我调用一个方法,然后删除

If you're removing the entry for the loop, you can do that via a list iterator 's remove method: 如果要删除循环的条目,则可以通过列表迭代器remove方法来实现:

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

(There are also add and set operations.) (还有addset操作。)

ConcurrentModificationException is not necessarily 'concurrent' in the sense of threading, it can be 'concurrent' in the sense of that you shall not directly modify a collection at the same time when you are iterating over it. 从线程的意义上讲, ConcurrentModificationException不一定是“并发的”,从迭代的意义上讲,您不应同时直接修改一个集合,而从某种意义上说,它可以是“并发的”。

It is in the docs too, for a long time (excerpt from Java7: https://docs.oracle.com/javase/7/docs/api/java/util/ConcurrentModificationException.html ) 它也存在于文档中很长一段时间(摘自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. 例如,如果线程在使用快速失败迭代器迭代集合时直接修改了集合,则迭代器将抛出此异常。

And for(x:y) uses an iterator, which can easily end up being a 'fail-fast' one. 并且for(x:y)使用一个迭代器,该迭代器很容易最终成为“快速失败”的迭代器。

Starting from your comment in the original question, you are removing items in the routingTable while you are iterating . 从对原始问题的评论开始,您将routingTable 迭代删除 routingTable 项目

You can't do it (also if the routingTable is synchronized) 不能这样做(即使routingTable是同步的)

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM