我得到了一个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
已经同步,它也会在循环中引发异常。每个类只能执行一次该线程。
有什么想法吗?
答案 0 :(得分:3)
有两种可能性:
您的类中有 other 代码,它们可以修改routingTable
,并且这样做时不会使用synchronized (routingTable)
。因此,当其他代码在该迭代过程中修改列表时,就会出现错误。
您正在修改列表中包含“ 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 iterator的remove
方法进行操作:
for (var it = routingTable.listIterator(); it.hasNext; ) {
var entry = it.next();
if (/*...some condition...*/) {
it.remove(); // Removes the current entry
}
}
(还有add
和set
个操作。)
答案 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(....);
}