Java 尽管使用了同步的 ConcurrentModificationException

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1655362/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-12 18:44:01  来源:igfitidea点击:

ConcurrentModificationException despite using synchronized

javaconcurrencyiterator

提问by dayscott

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

despite the synchronized statement in the declaration header, i still get a ConcurrentModificationException Exception at the line where i use iterator.next(); whats wrong here ?

尽管声明标头中有同步语句,但我仍然在使用 iterator.next() 的行处收到 ConcurrentModificationException 异常;怎么了?

采纳答案by Ramon

ConcurrentModificationExceptionusually 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. For example, this will cause it:

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

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

In this case you mustuse the iterator.remove()method instead. This occurs equally if you are adding to the collection, in which case there is no general solution. However, the subtype ListIteratorcan be used if dealing with a list and this has an add()method.

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

回答by Andy Turner

I agree with the statements above about ConcurrentModificationExceptionoften happening as a result of modifying the collection in the same thread as iterating. However, it is not alwaysthe reason.

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

The thing to remember about synchronizedis that it only guarantees exclusive access if everybody accessing the shared resource also synchronizes.

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

For example, you can synchronize access to a shared variable:

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

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

And you can thinkthat you have exclusive access to it. However, there is nothing to stop another thread just doing something without the synchronizedblock:

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

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

Through bad luck (or Murphy's Law, "Anything that can go wrong, will go wrong."), these two threads can happen to execute at the same time. In the case of structural modifications of some widely-used collections (e.g. ArrayList, HashSet, HashMapetc), this can result in a ConcurrentModificationException.

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

It is hard to prevent the problem entirely:

很难完全避免这个问题:

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

    There is the javax.annotation.concurrent.GuardedByannotation, 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 somesituations, but it's not perfect.

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

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

    Where mutexis the synchronized-on instance (often the SynchronizedCollectionitself), and cis 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.

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

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

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

    @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

回答by Akash5288

Below example is just the demo for this:

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

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

}

}