我有synchronized
方法在ArrayList
上运行,然后通过调用ArrayList.clear()
删除其元素。
当多个线程调用此方法时,为什么我得到ConcurrentModificationException
,即使它已同步?
用于避免此类错误的关键字synchronized
是不是?
public MyOuterClass {
private class MyInnerClass implements MyCallback {
private class MyInnermostClass implements Runnable {
@Override
public void run() {
if (condition) {
myMethod();
}
else {
synchronized (myArrayList) {
myArrayList.add(new MyObject());
}
}
}
public void myCallback() {
Thread myThread = new Thread(new MyInnermostClass());
myThread.start();
}
}
private ArrayList<MyObject> myArrayList;
public MyClass() {
myArrayList = new ArrayList<>();
}
private synchronized void myMethod() {
for (MyObject o: myArrayList) {
System.out.println(o);
}
myArrayList.clear();
}
}
堆栈追踪:
Exception in thread "Thread-9" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:939)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:893)
at my.package.MyOuterClass.myMethod(MyOuterClass.java:myLineNumber)
答案 0 :(得分:1)
如果从多个线程调用您的方法,则肯定需要同步。然而,这听起来像其他事情正在发生。
尽管它的名字,ConcurrentModificationException
与线程并发无关(固有地)。相反,当迭代器注意到迭代的集合以某种意外的方式被更改时,它会被抛出。例如,即使使用单线程应用程序,以下内容也会生成ConcurrentModificationException
:
for (Object o : myArrayList) {
if (shouldRemove(o)) {
myArrayList.remove(o);
}
}
处理这种情况的正确方法是使用显式迭代器并使用它来删除元素:
for (Iterator<Object> iter = myArrayList.iterator(), Object o;
iter.hasNext();
o = iter.next())
{
if (shouldRemove(o)) {
iter.remove();
}
}
您还可以使用ListIterator
(使用myArrayList.listIterator()
获得)在迭代时更多地控制列表内容(例如替换元素)。
您的方法需要synchronized
的原因是否则一个线程可能会调用myArrayList.clear()
而另一个线程仍在迭代,从而导致异常。还有可能同时调用myArrayList.clear()
可能会破坏列表的内部结构。
由于您的方法是同步的,我怀疑您有一个(或两个)这些问题:
System.out.println(o)
循环体内执行某些操作(除了调用for
之外)。其中任何一个都会导致异常。对于第一个问题,您需要确保修改myArrayList
的每段代码都已同步。不仅如此,它们还需要在同一个对象上同步。对于第二个问题,要么在迭代器完成后安排代码进行列表修改,要么使用显式迭代器并使用它进行所有更改。
编辑根据您编辑的问题,我的诊断是:您正在同步不同对象上的不同代码块,因此您仍在多个线程上同时执行代码。具体而言,在MyInnermostClass
中,您正在myArrayList
进行同步。在MyClass
中,它是同步的myMethod()
本身,它在MyClass
的实例上同步,而不是在阵列上同步。我认为,最好的办法是重新定义myMethod()
,如下所示:
private void myMethod() {
synchronized (myArrayList) {
for (MyObject o: myArrayList) {
System.out.println(o);
}
myArrayList.clear();
}
}
这样,修改myArrayList
的所有代码都会在列表对象本身上同步。