请帮助我理解我得到的错误:
private void replayHistory() {
synchronized (alarmsHistory) {
for (AlarmEvent alarmEvent : alarmsHistory) {
LOG.error("replayHistory " + alarmEvent.type + " " + alarmEvent.source);
sendNotification(alarmEvent.type, alarmEvent.source, alarmEvent.description,
alarmEvent.clearOnOtherStations, alarmEvent.forceClearOnOtherStations);
}
}
}
以及向其添加元素的方法
private void addToAlarmsHistory(AlarmEvent alarmEvent) {
synchronized (alarmsHistory) {
LOG.error("addToAlarmsHistory " + alarmEvent.type + " " + alarmEvent.source);
alarmsHistory.add(alarmEvent);
}
}
两种方法和Set
private volatile Set<AlarmEvent> alarmsHistory = new LinkedHashSet<AlarmEvent>();
在
中定义 JmxGwReloadThread extends Thread class
是
中的内部类 AlarmManager class
有方法
private void addToReplayHistory(AlarmEvent alarmEvent) {
if ((jmxThread != null) && (jmxThread.isAlive())) {
jmxThread.addToAlarmsHistory(alarmEvent);
}
}
由不同的接口调用(无法确定何时和多久)
在某些时候启动JmxThread并调用replayHistory方法
抛出 java.util.ConcurrentModificationException
,根来自
for (AlarmEvent alarmEvent : alarmsHistory) {
代码可以尝试向alarmsHistory和interator
添加元素java.util.ConcurrentModificationException
at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(LinkedHashMap.java:390)
at java.util.LinkedHashMap$KeyIterator.next(LinkedHashMap.java:401)
at AlarmManager$JmxGwReloadThread.replayHistory(AlarmManager.java:568)
at AlarmManager$JmxGwReloadThread.run(AlarmManager.java:532)
在调用nextEntry时抛出异常,但不应该同步阻止这样的问题吗?
日志显示同步不起作用 - replayHistory应遍历其所有元素(我可以确定它的多个单个HEARTBEAT_INFO FM),但它被addToReplayHistory调用中断。
2013-07-11 11:58:33,951 Thread-280 ERROR AlarmManager$JmxGwReloadThread.replayHistory(AlarmManager.java:570) - replayHistory HEARTBEAT_INFO FM
2013-07-11 11:58:33,951 Thread-280 ERROR AlarmManager$JmxGwReloadThread.addToAlarmsHistory(AlarmManager.java:550) - addToAlarmsHistory HEARTBEAT_INFO FM
2013-07-11 11:58:33,952 Thread-280 ERROR Log4jConfigurator$UncaughtExceptionHandler.uncaughtException(Log4jConfigurator.java:253) - Detected uncaught exception in thread: Thread-280
答案 0 :(得分:4)
OP(可能是大多数人)应该注意的一件事:
ConcurrentModificationException与多线程无关。
虽然多线程使其更容易发生,但此问题的核心与多线程无关。
这主要是由像
这样的场景引起的当然,并非所有收藏品都有此类行为,例如: ConcurrentHashMap的。 “结构修改”的定义也因不同的收集而有所不同。
这意味着,即使我只有一个帖子,如果我做的话:
List<String> strings = new ArrayList<String>();
//....
for (String s: strings) { // iterating through the collection
strings.add("x"); // structurally modifying the collection
}
我会得到ConcurrentModificationException,即使这一切都发生在单线程中。
根据您的要求或问题,有不同的方法可以解决问题。 e.g。
鉴于您的代码似乎在alarmHistory
上有正确的同步,您需要检查两个方向
alarmHistory
内sendNotification()
是否有任何可能的修改?例如,在alarmHistory
?答案 1 :(得分:2)
如果一个线程迭代,另一个线程添加,那么你就会被冲洗。
鉴于您的代码似乎要同步访问两个相关的代码块,请查找其他未同步的代码,这些代码会在alarmsHistory中添加/删除。
答案 2 :(得分:1)
唯一的想法让我觉得你在场景背后有一个错综复杂的逻辑。我认为sendNotification
以某种方式递归调用addToReplayHistory
。因此,多线程是一个红色的鲱鱼,日志文件只显示一个涉及的线程,并且在sendNotification之后不可避免地有addToReplayHistory调用,它会修改集合并中断交互器。
更多信息在javadoc中用于例外:
请注意,此异常并不总是表示对象具有 由另一个线程同时修改。如果是单线程 发出一系列违反合同的方法调用 一个对象,该对象可能抛出此异常。例如,如果是 线程在迭代时直接修改集合 使用失败快速迭代器进行集合,迭代器将抛出此异常 异常。
答案 3 :(得分:0)
向kan's answer添加一些详细信息:
Java中的synchronized
块是reentrant:
可重入同步
回想一下,线程无法获取另一个线程拥有的锁。但是一个线程可以获得它已经拥有的锁。允许线程多次获取相同的锁可启用重入同步。这描述了一种情况,其中同步代码直接或间接地调用也包含同步代码的方法,并且两组代码使用相同的锁。在没有可重入同步的情况下,同步代码必须采取许多额外的预防措施,以避免线程导致自身阻塞。
就像kan指出的那样,事实上可能是多个线程没有修改你的集合,但只有一个线程由于可重入的行为而可能总是获得锁定。
您应该寻找的修复异常的事项是同步块之间的递归调用,或alarmsHistory
的非同步访问。
您还可以查看并发集合,例如ConcurrentSkipList或CopyOnWriteArraySet。两者都应该防止异常,但要注意它们在JavaDoc中描述的行为和性能特征。