ConcurrentModificationException,尽pipe使用同步
public synchronized X getAnotherX(){ if(iterator.hasNext()){ X b = iterator.next(); String name = b.getInputFileName(); ... return b; } else{return null;} }
尽pipe在声明头文件中有synchronized语句,但是我仍然在我使用iterator.next()的那一行得到一个ConcurrentModificationExceptionexception。 这里怎么了?
ConcurrentModificationException
通常与多个线程无关。 大多数情况下,这是因为您正在修改迭代循环体内迭代的集合。 例如, 这将导致它:
Iterator iterator = collection.iterator(); while (iterator.hasNext()) { Item item = (Item) iterator.next(); if (item.satisfiesCondition()) { collection.remove(item); } }
在这种情况下,您必须改用iterator.remove()
方法。 如果您要添加到集合中,则会发生同样的情况,在这种情况下,没有常规解决scheme。 但是,如果处理一个列表并且具有add()
方法,则可以使用子typesListIterator
。
我同意上面有关ConcurrentModificationException
的声明,这经常是由于在迭代中修改同一线程中的集合而导致的。 但是,并不总是这个原因。
关于synchronized
的事情要记住,只有当访问共享资源的每个人都synchronized
,它才保证独占访问。
例如,您可以同步访问共享variables:
synchronized (foo) { foo.setBar(); }
而且你可以认为你有独占访问权限。 但是,没有什么可以阻止另一个线程在没有synchronized
块的情况下执行某些操作:
foo.setBar(); // No synchronization first.
通过运气不好(或墨菲定律 ,“任何可能出错的东西都会出问题的”),这两个线程可能会同时发生。 在一些广泛使用的集合(如ArrayList
, HashSet
, HashMap
等)进行结构修改的情况下,这可能会导致ConcurrentModificationException
。
很难完全防止这个问题:
-
您可以logging同步要求,例如,插入“您必须在修改此集合之前进行同步”或“先获取
bloo
locking”,但这要依靠用户来发现,阅读,理解和应用指令。有
javax.annotation.concurrent.GuardedBy
注释,它可以帮助以标准的方式logging这个注释; 那么问题是你必须有一些方法来检查工具链中注释的正确使用。 例如,您可能可以使用Google的errorprone ,它可以在某些情况下检查,但并不完美 。 -
对于集合的简单操作,可以使用
Collections.synchronizedXXX
工厂方法,它们包装一个集合,以便每个方法调用都首先在底层集合上进行SynchronizedCollection.add
,例如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
-