尽管使用synchronized,ConcurrentModificationException

day*_*ott 15 java concurrency iterator

 public synchronized X getAnotherX(){ 
  if(iterator.hasNext()){
   X b = iterator.next();
   String name = b.getInputFileName();
  ...
   return b;
  }
  else{return null;}
 }
Run Code Online (Sandbox Code Playgroud)

尽管声明头中的synchronized语句,我仍然在我使用iterator.next()的行中得到一个ConcurrentModificationException异常; 什么错了?

Ram*_*mon 39

ConcurrentModificationException通常与多线程无关.大多数情况下它会发生,因为您正在修改它在迭代循环体内迭代的集合.例如,这将导致它:

Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
    Item item = (Item) iterator.next();
    if (item.satisfiesCondition()) {
       collection.remove(item);
    }
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,您必须使用该iterator.remove()方法.如果要添加到集合中,则会同样发生这种情况,在这种情况下,没有通用解决方案.但是,ListIterator如果处理列表并且这有一个add()方法,则可以使用子类型.


And*_*ner 6

我同意上面关于ConcurrentModificationException经常发生的陈述,因为在迭代的同一线程中修改集合。然而,这并不总是原因。

需要记住的synchronized是,它仅在访问共享资源的每个人都同步时才保证独占访问。

例如,您可以同步对共享变量的访问:

synchronized (foo) {
  foo.setBar();
}
Run Code Online (Sandbox Code Playgroud)

您可以认为您可以独享它。但是,没有什么可以阻止另一个线程在没有synchronized块的情况下做一些事情:

foo.setBar();  // No synchronization first.
Run Code Online (Sandbox Code Playgroud)

由于运气不好(或墨菲定律,“任何可能出错的事情,都会出错。”),这两个线程可能碰巧同时执行。在一些广泛使用的集合(例如,结构修改的情况下ArrayListHashSetHashMap等等),这可能会导致一个ConcurrentModificationException

很难完全避免这个问题:

  • 您可以记录同步要求,例如插入“blah修改此集合之前必须同步”或“bloo首先获取锁定”,但这依赖于用户发现、阅读、理解和应用指令。

    javax.annotation.concurrent.GuardedBy注释,它可以帮助以标准化的方式记录这一点;问题是你必须有一些方法来检查工具链中注释的正确使用。例如,您可能可以使用Google 的 errorprone 之的东西,它可以在某些情况下进行检查,但它并不完美

  • 有关集合的简单的操作,您可以使用的Collections.synchronizedXXX工厂方法,它包裹的集合,使每一个方法调用底层集合下同步第一,如SynchronizedCollection.add

    @Override public boolean add(E e) {
      synchronized (mutex) { return c.add(obj); }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    哪里mutex是同步实例(通常是SynchronizedCollection它自己),c是包装的集合。

    这种方法的两个注意事项是:

    1. 您必须小心,不能以任何其他方式访问包装的集合,因为这将允许非同步访问,这是原始问题。这通常是通过在构建时立即包装集合来实现的:

      Collections.synchronizedList(new ArrayList<T>());
      
      Run Code Online (Sandbox Code Playgroud)
    2. 每个方法调用都会应用同步,因此如果您正在执行某些复合操作,例如

      if (c.size() > 5) { c.add(new Frob()); }
      
      Run Code Online (Sandbox Code Playgroud)

      那么您在整个操作过程中没有独占访问权限,只能单独进行size()add(...)调用。

      为了在复合操作期间获得互斥访问,您需要进行外部同步,例如synchronized (c) { ... }. 这需要您知道要同步的正确内容,但是,这可能是也可能不是c