【问题标题】:Java Concurrent Exception when using copy of lists [duplicate]使用列表副本时的Java并发异常[重复]
【发布时间】:2016-02-23 10:15:05
【问题描述】:

我的课堂上有以下代码:

private static LinkedList<MyObject> myList = 
                new LinkedList<MyObject>();

public static void doEventStuff(String user, String event){
        LinkedList<MyObject> copy;
        synchronized (myList) {
            copy = new LinkedList<>(myList);
        }
        for (MyObject o : copy) {
             ... do something with objects o
        }

}

public static void removeObject(MyObject o) {
        synchronized (myList) {
            myList.remove(o);
        }
        o.doCleanup();
    }

public static void terminate() {
        synchronized (myList) {
            for (MyObject o : myList) {
                o.doCleanup();
            }

            myList.clear();
        }

    }

public static List<MyObject> getMyObjectsCopy() {
        synchronized (myList) {
            return new LinkedList<>(myList);
        }
    }

我的问题是调用 terminate() 时出现 ConcurrentModificationException,特别是在迭代“for (MyObject o : myList)”时。

列表 myList 没有传递,只能通过静态方法访问。 另外:方法 MyObject.doCleanup() ca 触发事件,当在 terminate() mthod 中进行迭代时,可以调用方法“removeObject(MyObject)”,但由于所有方法同步 在“myList”上,我不相信会发生并发异常。

谁能帮我解决这个问题?

【问题讨论】:

  • 如果可以从列表中删除对象,请使用迭代器而不是 for 循环。在这样的 for 循环期间删除对象可能会导致该异常。
  • myList 进行了一些修改,您没有向我们展示。

标签: java concurrency


【解决方案1】:

在这段代码中:

for (MyObject o : myList) {
    o.doCleanup(o);
}

您调用代码,该代码在内部调用 removeObject() 方法。在这个调用中,我们创建了 myList.remove(o),这将改变一个列表,因此,它的工作方式如下:

for (MyObject o : myList) {
    myList.remove();
}

所以,这不是并发问题,它只是在 forEach 循环中对该集合进行了修改。我认为这种情况的最佳解决方案是避免在 doCleanup() 代码中从 myList 中删除,这看起来缺乏设计。 其他可能的解决方案 - 另一个不会引发导致从集合中删除的事件的 doCleanup() 方法版本 - 您已经执行了 myList.clear()。 或者重写 removeObject() 方法,如:

public static void removeObject(MyObject o) {
    synchronized (myList) {
        for (Iterator<MyObject> it = myList.iterator(); it.hasNext(); ) {
            MyObject o1 = it.next();
            if (o1.equals(o)) {
               it.remove();
            }
        }            
    }
    o.doCleanup();
}

据我所知,就像@geert3 在他的回答中推荐的那样,但这个答案的动机对我来说并不完全清楚。

但我不喜欢最后一个解决方案——它看起来像是一个设计问题的 hack,因为在这个全局集合维护代码中,我们在已删除对象上调用 doCleanup(),它应该在事件处理程序中再调用一个 removeObject()——我认为它最好去掉这个“递归”。

【讨论】:

    【解决方案2】:

    ConcurrentModificationException 如果在使用“foreach”循环对其进行迭代时修改了列表,也会发生这种情况。 synchronize 将有助于避免其他线程访问您的列表,但您的问题不是由于线程并发造成的。如果您想在迭代列表时删除(从同一个线程),您必须使用iterator 并调用iterator.remove()

    【讨论】:

    • iteration.remove 如何帮助从多线程环境中的非线程安全集合中删除? ConcurrentModificationException 本身主要是迭代器的问题。
    • @DmitrySpikhalskiy 更新了我的答案。同步对 CME 有帮助,但在这种情况下没有。
    • 我相信这是正确的答案... doCleanup() 方法中触发的事件没有到达(并且没有从我的外部回调中调用方法)在单独的线程上,请在removeObject(...) 方法实际上并没有做任何事情...
    【解决方案3】:

    这本身不是multi-threading 问题,如果您在foreach 循环中从列表中删除一个对象,您将得到ConcurrentModificationException。 顺便说一句,你可以改用CopyOnWriteArrayList

    【讨论】:

      猜你喜欢
      • 2018-05-29
      • 1970-01-01
      • 1970-01-01
      • 2012-10-29
      • 2021-07-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-12
      相关资源
      最近更新 更多