尽管使用synchronized,ConcurrentModificationException

时间:2009-10-31 19:32:20

标签: java concurrency iterator

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

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

3 个答案:

答案 0 :(得分:39)

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

Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
    Item item = (Item) iterator.next();
    if (item.satisfiesCondition()) {
       collection.remove(item);
    }
}

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

答案 1 :(得分:4)

我同意上面关于ConcurrentModificationException经常发生的声明,因为修改了与迭代相同的线程中的集合。但是,它不是总是的原因。

关于synchronized要记住的事情是,如果访问共享资源的每个人也同步,它只保证独占访问。

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

synchronized (foo) {
  foo.setBar();
}

您可以认为您拥有对它的独占访问权。但是,没有任何东西可以阻止另一个线程在没有synchronized块的情况下执行某些操作:

foo.setBar();  // No synchronization first.

运气不好(或Murphy's Law,"任何可能出错的东西,都会出错。"),这两个线程可能会同时执行。对于一些广泛使用的集合(例如ArrayListHashSetHashMap等)的结构修改,这可能会产生ConcurrentModificationException

很难完全避免这个问题:

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

    javax.annotation.concurrent.GuardedBy注释,可以帮助以标准化方式记录这一点;问题是你必须有一些方法来检查工具链中正确使用注释。例如,您可以使用Google's errorprone之类的内容,可以检查某些情况,但it's not perfect

  • 对于集合的简单操作,您可以使用Collections.synchronizedXXX工厂方法,它们包装集合,以便每个方法调用首先在底层集合上同步,例如the SynchronizedCollection.add method

    @Override public boolean add(E e) {
      synchronized (mutex) { return c.add(obj); }
    }
    

    mutex是同步开启的实例(通常是SynchronizedCollection本身),而c是包装的集合。

    采用这种方法的两个警告是:

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

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

      if (c.size() > 5) { c.add(new Frob()); }
      

      然后,您不会在整个操作过程中拥有独占访问权限,只能分别针对size()add(...)来电。

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

答案 2 :(得分:0)

下面的示例仅是此示例:

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");
}

}