我同意上面关于经常发生的陈述,因为在迭代的同一线程中修改集合。但是,这并不总是原因。ConcurrentModificationException
要记住的是,只有当访问共享资源的每个人都同步时,它才能保证独占访问。synchronized
例如,您可以同步对共享变量的访问:
synchronized (foo) {
foo.setBar();
}
你可以认为你有独家访问它。但是,没有什么可以阻止另一个线程在没有块的情况下做一些事情:synchronized
foo.setBar(); // No synchronization first.
通过坏运气(或墨菲定律,“任何可能出错的东西,都会出错”),这两个线程可以碰巧同时执行。在对一些广泛使用的集合(例如 、 等)进行结构修改的情况下,这可能导致 .ArrayList
HashSet
HashMap
ConcurrentModificationException
很难完全防止这个问题:
-
您可以记录同步要求,例如,插入“在修改此集合之前必须同步”或“首先获取锁”,但这依赖于用户来发现,阅读,理解和应用指令。blah
bloo
有注释,可以帮助以标准化的方式记录这一点;问题是,您必须有一些方法来检查工具链中注释的正确使用。例如,您可能能够使用像Google的errorprone这样的东西,它可以在某些情况下进行检查,但它并不完美。javax.annotation.concurrent.GuardedBy
-
对于对集合的简单操作,您可以使用 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