public synchronized X getAnotherX(){
if(iterator.hasNext()){
X b = iterator.next();
String name = b.getInputFileName();
...
return b;
}
else{return null;}
}
尽管声明头中的synchronized语句,我仍然在使用iterator.next()的行中得到一个ConcurrentModificationException异常;什么错了?
答案 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,"任何可能出错的东西,都会出错。"),这两个线程可能会同时执行。对于一些广泛使用的集合(例如ArrayList
,HashSet
,HashMap
等)的结构修改,这可能会产生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
是包装的集合。
采用这种方法的两个警告是:
您必须小心,无法以任何其他方式访问包装的集合,因为这将允许非同步访问,原始问题。这通常通过立即将集合包装在构造上来实现:
Collections.synchronizedList(new ArrayList<T>());
每次方法调用都会应用同步,因此如果您正在进行某些复合操作,例如
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");
}
}