Java 尽管使用了同步的 ConcurrentModificationException
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1655362/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
ConcurrentModificationException despite using synchronized
提问by dayscott
public synchronized X getAnotherX(){
if(iterator.hasNext()){
X b = iterator.next();
String name = b.getInputFileName();
...
return b;
}
else{return null;}
}
despite the synchronized statement in the declaration header, i still get a ConcurrentModificationException Exception at the line where i use iterator.next(); whats wrong here ?
尽管声明标头中有同步语句,但我仍然在使用 iterator.next() 的行处收到 ConcurrentModificationException 异常;怎么了?
采纳答案by Ramon
ConcurrentModificationException
usually has nothing to do with multiple threads. Most of the time it occurs because you are modifying the collection over which it is iterating within the body of the iteration loop. For example, this will cause it:
ConcurrentModificationException
通常与多线程无关。大多数情况下,它发生是因为您正在修改它在迭代循环体内迭代的集合。例如,这将导致它:
Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
Item item = (Item) iterator.next();
if (item.satisfiesCondition()) {
collection.remove(item);
}
}
In this case you mustuse the iterator.remove()
method instead. This occurs equally if you are adding to the collection, in which case there is no general solution. However, the subtype ListIterator
can be used if dealing with a list and this has an add()
method.
在这种情况下,您必须改用该iterator.remove()
方法。如果您要添加到集合中,这同样会发生,在这种情况下,没有通用的解决方案。但是,ListIterator
如果处理列表并且它具有add()
方法,则可以使用子类型。
回答by Andy Turner
I agree with the statements above about ConcurrentModificationException
often happening as a result of modifying the collection in the same thread as iterating. However, it is not alwaysthe reason.
我同意上面关于ConcurrentModificationException
经常发生的陈述,因为在迭代的同一线程中修改集合。然而,这并不总是原因。
The thing to remember about synchronized
is that it only guarantees exclusive access if everybody accessing the shared resource also synchronizes.
需要记住的synchronized
是,它仅在访问共享资源的每个人也同步时才保证独占访问。
For example, you can synchronize access to a shared variable:
例如,您可以同步对共享变量的访问:
synchronized (foo) {
foo.setBar();
}
And you can thinkthat you have exclusive access to it. However, there is nothing to stop another thread just doing something without the synchronized
block:
您可以认为您可以独享它。但是,没有什么可以阻止另一个线程在没有synchronized
块的情况下做一些事情:
foo.setBar(); // No synchronization first.
Through bad luck (or Murphy's Law, "Anything that can go wrong, will go wrong."), these two threads can happen to execute at the same time. In the case of structural modifications of some widely-used collections (e.g. ArrayList
, HashSet
, HashMap
etc), this can result in a ConcurrentModificationException
.
由于运气不好(或墨菲定律,“任何可能出错的事情,都会出错。”),这两个线程可能恰好同时执行。在一些广泛使用的集合(例如,结构修改的情况下ArrayList
,HashSet
,HashMap
等等),这可能会导致一个ConcurrentModificationException
。
It is hard to prevent the problem entirely:
很难完全避免这个问题:
You can document synchronization requirements, e.g. inserting "you must synchronize on
blah
before modifying this collection" or "acquirebloo
lock first", but that's relying upon users to discover, read, understand and apply the instruction.There is the
javax.annotation.concurrent.GuardedBy
annotation, which can help to document this in a standardized way; the problem is then that you have to have some means of checking correct use of the annotation in the toolchain. For example, you might be able to use something like Google's errorprone, which can check in somesituations, but it's not perfect.For simple operations on collections, you can make use of the
Collections.synchronizedXXX
factory methods, which wrap a collection so that every method call synchronizes on the underlying collection first, e.g. theSynchronizedCollection.add
method:@Override public boolean add(E e) { synchronized (mutex) { return c.add(obj); } }
Where
mutex
is the synchronized-on instance (often theSynchronizedCollection
itself), andc
is the wrapped collection.The two caveats with this approach are:
You have to be careful that the wrapped collection cannot be accessed in any other way, since that would allow non-synchronized access, the original problem. This is typically achieved by wrapping the collection immediately on construction:
Collections.synchronizedList(new ArrayList<T>());
The synchronization is applied per method call, so if you are doing some compound operation, e.g.
if (c.size() > 5) { c.add(new Frob()); }
then you don't have exclusive access throughout that operation, only for the
size()
andadd(...)
calls individually.In order to get mutually exclusive access for the duration of the compound operation, you would need to externally synchronize, e.g.
synchronized (c) { ... }
. This requires you to know the correct thing to synchronize on, however, which may or may not bec
.
您可以记录同步要求,例如插入“
blah
修改此集合之前必须同步”或“bloo
首先获取锁定”,但这依赖于用户发现、阅读、理解和应用指令。有
javax.annotation.concurrent.GuardedBy
注释,它可以帮助以标准化的方式记录这一点;问题是你必须有一些方法来检查工具链中注释的正确使用。例如,您可能可以使用Google 的 errorprone 之类的东西,它可以在某些情况下进行检查,但它并不完美。有关集合的简单的操作,您可以使用的
Collections.synchronizedXXX
工厂方法,它包裹的集合,使每一个方法调用底层集合下同步第一,如该SynchronizedCollection.add
法:@Override public boolean add(E e) { synchronized (mutex) { return c.add(obj); } }
哪里
mutex
是同步实例(通常是SynchronizedCollection
它自己),c
是包装的集合。这种方法的两个注意事项是:
您必须小心,不能以任何其他方式访问包装的集合,因为这将允许非同步访问,这是原始问题。这通常是通过在构建时立即包装集合来实现的:
Collections.synchronizedList(new ArrayList<T>());
每个方法调用都会应用同步,因此如果您正在执行某些复合操作,例如
if (c.size() > 5) { c.add(new Frob()); }
那么您在整个操作过程中没有独占访问权限,只能单独进行
size()
和add(...)
调用。为了在复合操作期间获得互斥访问,您需要进行外部同步,例如
synchronized (c) { ... }
. 这需要您知道要同步的正确内容,但是,这可能是也可能不是c
。
回答by Akash5288
Below example is just the demo for this:
下面的例子只是这个演示:
public class SynchronizedListDemo {
public static void main(String[] args) throws InterruptedException {
List<Integer> list = new ArrayList<>();
for(int i=0;i<100000;i++){
System.out.println(i);
list.add(i);
}
// Synchronzied list will also give ConcurrentModificationException, b'coz
// it makes only methods thread safe, but you are still modifying list while iterating it.
// You can use 'ListIterator' or 'CopyOnWriteArrayList'
List<Integer> list1 = Collections.synchronizedList(list);
Runnable r1= ()->{
for(Integer i: list1)
System.out.println(i);
};
Runnable r2 = ()->{
try {
System.out.println();
System.out.println("Removing....");
//list1.add(4); // Will give ConcurrentModificationException
System.out.println("Removed");
} catch (Exception e) {
e.printStackTrace();
}
};
// This will not give ConcurrentModificationException as it work on the copy of list.
List<Integer> list2 = new CopyOnWriteArrayList<>(list);
Runnable r3= ()->{
for(Integer i: list2)
System.out.println(i);
};
Runnable r4 = ()->{
try {
System.out.println();
System.out.println("Removing....");
list2.add(4);
System.out.println("Removed");
} catch (Exception e) {
e.printStackTrace();
}
};
Thread t1 = new Thread(r3);
Thread.sleep(100);
Thread t2 = new Thread(r4);
t1.start();
t2.start();
System.out.println("Done");
}
}
}