使用列表副本时的Java并发异常

时间:2015-11-20 19:47:45

标签: java concurrency

我的课程中有以下代码:

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触发事件,其中可以调用方法“removeObject(MyObject)”,当在finally()mthod内部进行迭代时,但由于所有方法都同步 在“myList”上,我不相信会发生并发异常。

任何人都可以帮我解决这个问题吗?

3 个答案:

答案 0 :(得分:2)

如果在使用'foreach'循环迭代它时修改了列表,也会发生

ConcurrentModificationExceptionsynchronize将帮助避免其他线程访问您的列表,但您的问题不是由于线程并发。如果要在迭代列表时删除(从同一个线程),则必须使用iterator并调用iterator.remove()

答案 1 :(得分:2)

这本身不是multi-threading问题,如果您从foreach循环中的列表中删除对象,您将获得ConcurrentModificationException。 顺便说一下,您可以使用CopyOnWriteArrayList代替

答案 2 :(得分:0)

在此代码中:

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在他的回答中建议,但这个答案的动机并不完全清楚。

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