这是我的类,有两个方法修改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)
现在它让我发疯了!任何人都可以帮忙找到错误吗?或其他方式做同样的事情?
答案 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();
}
}
注意:这不需要包含在线程中。
我检查是否启用了日志记录,因为生成不需要的字符串可能会非常慢,而且通常最简单的方法是加速应用程序。