LinkedHashSet迭代时的java.util.ConcurrentModificationException

时间:2013-07-11 08:38:44

标签: java concurrentmodification

请帮助我理解我得到的错误:

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

4 个答案:

答案 0 :(得分:4)

OP(可能是大多数人)应该注意的一件事:

ConcurrentModificationException与多线程无关。

虽然多线程使其更容易发生,但此问题的核心与多线程无关。

这主要是由像

这样的场景引起的
  1. 从集合中获取迭代器
  2. 在使用该迭代器之前,该集合已在结构上进行了修改。
  3. 在2.之后继续使用迭代器。迭代器将检测集合是否在结构上被修改并且将抛出ConcurrentModificationException。
  4. 当然,并非所有收藏品都有此类行为,例如: 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。

    1. 如果仅仅是由于多线程访问,正确的同步访问可以是一种解决方案
    2. 您可以使用Collection,迭代器可以安全地进行结构修改(例如ConcurrentHashMap)
    3. 调整逻辑,以便在修改集合时再次重新获取迭代器,或者使用迭代器进行修改(某些集合impls允许),或者确保在完成迭代器使用后对集合进行修改。 / LI>

      鉴于您的代码似乎在alarmHistory上有正确的同步,您需要检查两个方向

      1. alarmHistorysendNotification()是否有任何可能的修改?例如,在alarmHistory
      2. 中添加或删除
      3. 是否有其他可能的不同步访问alarmHistory可能会修改结构?

答案 1 :(得分:2)

如果一个线程迭代,另一个线程添加,那么你就会被冲洗。

鉴于您的代码似乎要同步访问两个相关的代码块,请查找其他未同步的代码,这些代码会在alarmsHistory中添加/删除。

答案 2 :(得分:1)

唯一的想法让我觉得你在场景背后有一个错综复杂的逻辑。我认为sendNotification以某种方式递归调用addToReplayHistory。因此,多线程是一个红色的鲱鱼,日志文件只显示一个涉及的线程,并且在sendNotification之后不可避免地有addToReplayHistory调用,它会修改集合并中断交互器。

更多信息在javadoc中用于例外:

  

请注意,此异常并不总是表示对象具有   由另一个线程同时修改。如果是单线程   发出一系列违反合同的方法调用   一个对象,该对象可能抛出此异常。例如,如果是   线程在迭代时直接修改集合   使用失败快速迭代器进行集合,迭代器将抛出此异常   异常。

答案 3 :(得分:0)

kan's answer添加一些详细信息:

Java中的synchronized块是reentrant

可重入同步

回想一下,线程无法获取另一个线程拥有的锁。但是一个线程可以获得它已经拥有的锁。允许线程多次获取相同的锁可启用重入同步。这描述了一种情况,其中同步代码直接或间接地调用也包含同步代码的方法,并且两组代码使用相同的锁。在没有可重入同步的情况下,同步代码必须采取许多额外的预防措施,以避免线程导致自身阻塞。

就像kan指出的那样,事实上可能是多个线程没有修改你的集合,但只有一个线程由于可重入的行为而可能总是获得锁定。

您应该寻找的修复异常的事项是同步块之间的递归调用,或alarmsHistory的非同步访问。

您还可以查看并发集合,例如ConcurrentSkipListCopyOnWriteArraySet。两者都应该防止异常,但要注意它们在JavaDoc中描述的行为和性能特征。