删除synchronized方法中的ArrayList元素

时间:2018-02-13 20:55:03

标签: java synchronization

我有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)

1 个答案:

答案 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()可能会破坏列表的内部结构。

由于您的方法是同步的,我怀疑您有一个(或两个)这些问题:

  1. 您正在同时修改您尚未发布的其他代码中的另一个主题上的列表;
  2. 您正在修改列表的System.out.println(o)循环体内执行某些操作(除了调用for之外)。
  3. 其中任何一个都会导致异常。对于第一个问题,您需要确保修改myArrayList每段代码都已同步。不仅如此,它们还需要在同一个对象上同步。对于第二个问题,要么在迭代器完成后安排代码进行列表修改,要么使用显式迭代器并使用它进行所有更改。

    编辑根据您编辑的问题,我的诊断是:您正在同步不同对象上的不同代码块,因此您仍在多个线程上同时执行代码。具体而言,在MyInnermostClass中,您正在myArrayList进行同步。在MyClass中,它是同步的myMethod()本身,它在MyClass的实例上同步,而不是在阵列上同步。我认为,最好的办法是重新定义myMethod(),如下所示:

    private void myMethod() {
        synchronized (myArrayList) {
            for (MyObject o: myArrayList) {
                System.out.println(o);
            }
            myArrayList.clear();
        }
    }
    

    这样,修改myArrayList的所有代码都会在列表对象本身上同步。