尽管使用了同步,仍然出现ConcurrentModificationException异常

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

尽管在声明头部有同步语句,但我仍然会在使用iterator.next()的那一行代码抛出ConcurrentModificationException异常,这里出了什么问题?


1
请参见https://dev59.com/A3VC5IYBdhLWcg3wpi98。 - Raedwald
可能是为什么会抛出ConcurrentModificationException异常的重复问题。 - Raedwald
3个回答

44

ConcurrentModificationException通常与多个线程无关。大部分情况下,它发生是因为在迭代循环体内修改了正在迭代的集合。例如,以下操作会导致此异常:

Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
    Item item = (Item) iterator.next();
    if (item.satisfiesCondition()) {
       collection.remove(item);
    }
}
在这种情况下,你必须使用iterator.remove()方法。如果你要添加到集合中,同样会出现此情况,其中没有通用的解决方案。不过,如果处理的是列表,可以使用子类型ListIterator,它有一个add()方法。

1
我不明白,我只是想要一个字符串(在这种情况下是对象“b”中的字符串)。但是,我尝试使用iterator.remove();但那并没有帮助。相同的异常出现。 - dayscott
1
public synchronized Block getAnotherBlock(){ Block b = null; if(iterator.hasNext()){ b = iterator.next(); iterator.remove(); } String name = b.getInputFileName(); Integer []arr = blocksPerFileLeft.get(name); arr[1] += 1; blocksPerFileLeft.put(b.getInputFileName(), arr); currBlockPos++; //增加全局变量 return b; } - dayscott
1
好的,这很好,但在这种情况下,如果在取出迭代器(即调用iterator()方法)后对列表进行任何修改,即使每次访问集合都是同步的,也会导致ConcurrentModificationException。您不能将对迭代器方法的调用与对集合的突变交错使用。要了解其原因,请考虑迭代器的实现方式以及如果有人在迭代器当前位置之前或之后插入或删除元素时您期望发生什么。 - Ramon
1
在我看来,“ConcurrentModificationException”通常与多线程无关。大多数情况下,它出现的原因是你在迭代循环体内修改了正在被迭代的集合本身。当我遇到这个错误时,我会首先想到这一点。 - Jimmy
1
我认为这主要是由于多线程造成的。如果一个线程正在修改,而另一个线程已经在迭代,那么就会出现这种情况。如果您可以避免使用迭代器,那就太好了。迭代器就像创建快照一样,当迭代器被创建时,然后不断检查集合是否被修改。 - Jackie
显示剩余9条评论

6

我同意以上有关ConcurrentModificationException的陈述,通常发生在在同一线程中修改集合时进行迭代。然而,并不总是这个原因。

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

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

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

您可能认为您拥有对其的独占访问权限。然而,没有任何阻止另一个线程在没有synchronized块的情况下执行某些操作:

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

由于不幸的原因(或 墨菲定律:“任何可能出错的事情,终将出错”),这两个线程可能同时执行。对于一些广泛使用的集合结构修改(例如ArrayListHashSetHashMap等),这可能会导致ConcurrentModificationException

完全避免这个问题是很困难的:

  • You can document synchronization requirements, e.g. inserting "you must synchronize on blah before modifying this collection" or "acquire bloo lock first", but that's relying upon users to discover, read, understand and apply the instruction.

    There is the javax.annotation.concurrent.GuardedBy annotation, which can help to document this in a standardized way; the problem is then that you have to have some means of checking correct use of the annotation in the toolchain. For example, you might be able to use something like Google's errorprone, which can check in some situations, but it's not perfect.

  • For simple operations on collections, you can make use of the Collections.synchronizedXXX factory methods, which wrap a collection so that every method call synchronizes on the underlying collection first, e.g. the SynchronizedCollection.add method:

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

    Where mutex is the synchronized-on instance (often the SynchronizedCollection itself), and c is the wrapped collection.

    The two caveats with this approach are:

    1. You have to be careful that the wrapped collection cannot be accessed in any other way, since that would allow non-synchronized access, the original problem. This is typically achieved by wrapping the collection immediately on construction:

      Collections.synchronizedList(new ArrayList<T>());
      
    2. The synchronization is applied per method call, so if you are doing some compound operation, e.g.

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

      then you don't have exclusive access throughout that operation, only for the size() and add(...) calls individually.

      In order to get mutually exclusive access for the duration of the compound operation, you would need to externally synchronize, e.g. synchronized (c) { ... }. This requires you to know the correct thing to synchronize on, however, which may or may not be c.


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

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接