Java ConcurrentModificationException尽管使用了synchronized

Java ConcurrentModificationException尽管使用了synchronized,java,concurrency,iterator,Java,Concurrency,Iterator,尽管声明头中有synchronized语句,但在使用iterator.next()的行中仍然会出现ConcurrentModificationException异常;这里怎么了 ConcurrentModificationException通常与多线程无关。大多数情况下,它发生是因为您正在修改它在迭代循环体中迭代的集合。例如,这将导致它: public synchronized X getAnotherX(){ if(iterator.hasNext()){ X b = itera

尽管声明头中有synchronized语句,但在使用iterator.next()的行中仍然会出现ConcurrentModificationException异常;这里怎么了

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

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

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

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

关于
synchronized
需要记住的是,只有当访问共享资源的每个人都同步时,它才保证独占访问

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

Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
    Item item = (Item) iterator.next();
    if (item.satisfiesCondition()) {
       collection.remove(item);
    }
}
你可以认为你有独家访问权。但是,如果没有
synchronized
块,就无法阻止另一个线程执行某些操作:

synchronized (foo) {
  foo.setBar();
}
由于运气不好(或者“任何可能出错的事情都会出错”),这两个线程可能会同时执行。在对一些广泛使用的集合进行结构修改的情况下(例如,
ArrayList
HashSet
HashMap
等),这可能会导致
ConcurrentModificationException

很难完全防止这个问题:

  • 您可以记录同步要求,例如插入“您必须在修改此集合之前在
    blah
    上同步”或“获取
    bloo
    lock first”,但这取决于用户发现、阅读、理解和应用说明

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

  • 对于集合上的简单操作,可以使用工厂方法,这些方法包装集合,以便每个方法调用首先在基础集合上同步,例如:

    其中,
    mutex
    是synchronized on实例(通常是
    SynchronizedCollection
    本身),而
    c
    是包装的集合

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

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

    @Override public boolean add(E e) {
      synchronized (mutex) { return c.add(obj); }
    }
    
    然后,在整个操作过程中,您没有独占访问权,仅对
    size()
    add(…)
    调用单独进行访问

    为了在复合操作期间获得互斥访问,您需要进行外部同步,例如
    synchronized(c){…}
    。但是,这要求您知道要同步的正确对象,它可能是
    c


下面的示例就是这方面的演示:

if (c.size() > 5) { c.add(new Frob()); }
公共类SynchronizedListDemo{ 公共静态void main(字符串[]args)引发InterruptedException{ 列表=新的ArrayList();
对于(int i=0;ii)我不明白,我只是想要一个字符串(在本例中是在名为“b”的对象中)。但我尝试使用迭代器.remove();但没有帮助。同样的异常也会出现。没有问题:公共同步块getAnotherBlock(){Block b=null;if(iterator.hasNext()){b=iterator.next();iterator.remove();}String name=b.getInputFileName();Integer[]arr=blocksperfileft.get(name);arr[1]+=1;blocksperfileft.put(b.getInputFileName(),arr);currBlockPos++;//incr global var return b;好的,这很好,但在本例中,在取出迭代器(即调用
迭代器()后,对列表进行任何修改)
方法)即使对集合的每次访问都是同步的,也将导致
ConcurrentModificationException
。您不能将对迭代器方法的调用与集合的突变交织在一起。要了解原因,请考虑迭代器是如何实现的,以及如果有人在集合之前或之后插入或删除元素,会发生什么情况在迭代器的当前位置之后。+1在语句中:
ConcurrentModificationException通常与多个线程无关。大多数情况下,它发生是因为您正在修改它在迭代循环体中迭代的集合。
。这是我在遇到此错误我确实认为它主要是由于多线程造成的。如果一个线程正在修改,而另一个线程已经在迭代。如果可以避免使用迭代器,那就太好了。迭代器就像在迭代器创建时创建快照,然后继续检查集合是否已修改。请参阅可能的重复
Collections.synchronizedList(new ArrayList<T>());
if (c.size() > 5) { c.add(new Frob()); }
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");
}