【问题标题】:ConcurrentModificationException despite using synchronizedConcurrentModificationException 尽管使用了同步
【发布时间】:2010-12-11 22:32:34
【问题描述】:
 public synchronized X getAnotherX(){ 
  if(iterator.hasNext()){
   X b = iterator.next();
   String name = b.getInputFileName();
  ...
   return b;
  }
  else{return null;}
 }

尽管声明头中有同步语句,但在我使用 iterator.next() 的行处我仍然得到 ConcurrentModificationException 异常;这里有什么问题?

【问题讨论】:

标签: java concurrency iterator


【解决方案1】:

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

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

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

【讨论】:

  • 我不明白,我只是想要一个字符串,它是(在这种情况下是名为“b”的对象)。但是我尝试使用 iterator.remove();但这并没有帮助。同样的例外来了。
  • 没问题:public synchronized Block getAnotherBlock(){ Block b = null; if(iterator.hasNext()){ b = iterator.next();迭代器.remove(); } 字符串名称 = b.getInputFileName();整数 []arr = blocksPerFileLeft.get(name); arr[1] += 1; blocksPerFileLeft.put(b.getInputFileName(), arr); currBlockPos++; //增加全局变量返回b;
  • 好的,没关系,但在这种情况下,在您取出迭代器(即称为iterator() 方法)之后对列表的任何 修改将导致ConcurrentModificationException即使对集合的每次访问都是同步的。您不能将对迭代器方法的调用与对集合的突变交错。要了解其原因,请考虑迭代器是如何实现的,以及如果有人在迭代器的当前位置之前或之后插入或删除元素,您期望会发生什么。
  • 声明+1:ConcurrentModificationException usually has nothing to do with multiple threads. Most of the time it occurs because you are modifying the collection over which it is iterating within the body of the iteration loop。这是我遇到这个错误时对自己说的第一件事
  • 我确实认为它主要是由于多线程而发生的。如果一个线程正在修改,而另一个线程已经在迭代。如果您可以避免使用迭代器,那就太好了。迭代器就像在创建迭代器时创建快照,然后继续检查集合是否被修改。
【解决方案2】:

我同意上面关于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,也可能不是。

【讨论】:

    【解决方案3】:

    下面的例子只是这个演示:

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

    }

    【讨论】:

      猜你喜欢
      • 2018-05-18
      • 1970-01-01
      • 1970-01-01
      • 2013-02-26
      • 1970-01-01
      • 2013-03-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多