已经同步,但得到了ConcurrentModificationException

时间:2013-10-13 14:09:55

标签: java multithreading race-condition synchronized

这是我的类,有两个方法修改List PacketQueue。这两个方法在两个线程中执行,因此synchronize被标记。

public class MessageHandler implements nuctrl.interfaces.MessageHandler, Runnable {
    private static final List<GatewayMsg> PacketQueue = new LinkedList<GatewayMsg>();

    @Override
    public void insert(GatewayMsg msg) {
        synchronized (PacketQueue){
            PacketQueue.add(msg);
            PacketQueue.notify();
        }
        log.debug("insert " + msg.toString());
    }

    @Override
    public void run() {
        while(running){
            synchronized (PacketQueue){
                try {
                    while(PacketQueue.size() == 0){
                        PacketQueue.wait();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
                }
                for (GatewayMsg msg : PacketQueue){
                    PacketQueue.remove(msg);
                    packetHandler.onPacket(msg);//method call
                }
            }
        }
    }
}

run()用于 thread-4 insert()用于另一个帖子( I / O Worker#1 )。已添加Synchronized,一切似乎都没问题,但我仍然一直收到ConcurrentModificationException。

DEBUG [New I/O worker #1] (MessageHandler.java:47)| insert GatewayMsg<>
Exception in thread "Thread-4" java.util.ConcurrentModificationException
    at java.util.LinkedList$ListItr.checkForComodification(LinkedList.java:761)
    at java.util.LinkedList$ListItr.next(LinkedList.java:696)
    at nuctrl.core.MessageHandler.run(MessageHandler.java:67)
    at java.lang.Thread.run(Thread.java:680)
现在它让我发疯了!任何人都可以帮忙找到错误吗?或其他方式做同样的事情?

3 个答案:

答案 0 :(得分:4)

如果同步代码在迭代期间修改了集合,则同步不会阻止ConcurrentModificationException,您可以在此处执行此操作:

for (GatewayMsg msg : PacketQueue){
    PacketQueue.remove(msg);     // <== Not allowed during iteration
    packetHandler.onPacket(msg);
}

在迭代过程中,您只能通过 Iterator删除元素,例如:

Iterator<GatewayMsg> it = PacketQueue.iterator();
while (it.hasNext()) {
    GatewayMsg msg = it.next();
    it.remove();                 // <== This is allowed, provided the collection supports it
    packetHandler.onPacket(msg);
}

答案 1 :(得分:4)

这与同步无关 - 这样的代码即使在单个线程中也会触发异常:

for (GatewayMsg msg : PacketQueue){
    PacketQueue.remove(msg);
    packetHandler.onPacket(msg);
}

这是因为您正在修改正在迭代的集合。

要解决此问题,请在循环中使用列表迭代器,并调用迭代器的remove。更好的是,处理循环中的所有项目,然后立即清除PacketQueue,如下所示:

for (GatewayMsg msg : PacketQueue){
    packetHandler.onPacket(msg);
}
PacketQueue.clear();

这样可以正常工作,因为对PacketQueue的访问是同步的:其他线程在处理部分消息的状态下不会看到PacketQueue,但它们仍然保留在队列中。 / p>

答案 2 :(得分:0)

获得CME的原因是因为您在迭代时进行修改。该库无法区分您的线程和修改它的另一个线程。


最简单的解决方案是不要自己编写此队列/线程处理代码。我会用ExecutorService

来编写它
public class MessageHandler implements nuctrl.interfaces.MessageHandler {
    private static final ExecutorService EXEC = Executors.newSingleThreadExecutor();

    @Override
    public void insert(final GatewayMsg msg) {
        EXEC.submit(new Runnable() {
            @Override
            public void run() {
                packetHandler.onPacket(msg);//method call
            }
        });
        if(log.isDebugEnabled())
            log.debug("submitted " + msg);
    }

    public static void stop() {
        EXEC.shutdown();
    }
}

注意:这不需要包含在线程中。

我检查是否启用了日志记录,因为生成不需要的字符串可能会非常慢,而且通常最简单的方法是加速应用程序。