3

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

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 和when 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

4 回答 4

4

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

ConcurrentModificationException 与多线程无关。

虽然多线程让它更容易发生,但这个问题的核心与多线程无关。

这主要是由以下情况引起的,

  1. 从集合中获取迭代器,
  2. 在完成使用该迭代器之前,对集合进行结构修改。
  3. 2后继续使用迭代器。迭代器会检测到集合被结构修改,并抛出ConcurrentModificationException。

当然,并不是所有的集合都有这样的行为,例如 ConcurrentHashMap。对于不同的集合,“结构修改”的定义也不同。

这意味着,即使我只有 1 个线程,如果我执行以下操作:

    List<String> strings = new ArrayList<String>();
    //....
    for (String s: strings) {  // iterating through the collection
        strings.add("x");   // structurally modifying the collection
    }

即使这一切都发生在单线程中,我也会得到 ConcurrentModificationException 。

根据您的要求或问题,有不同的方法可以解决问题。例如

  1. 如果只是由于多线程访问,适当的同步访问可以是一种解决方案
  2. 您可以使用 Collection 使迭代器免受结构修改的影响(例如 ConcurrentHashMap)
  3. 如果您修改了集合,则调整您的逻辑以再次重新获取迭代器,或者使用迭代器进行修改(某些集合实现允许这样做),或者确保在完成使用迭代器后对集合进行修改。

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

  1. alarmHistoryinside有什么可能的修改sendNotification()吗?例如,添加或删除alarmHistory?
  2. 是否存在其他可能修改结构的对 alarmHistory 的非同步访问?
于 2013-07-12T03:57:35.910 回答
2

如果一个线程迭代,而另一个线程添加,你会被淹没。

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

于 2013-07-11T09:02:17.210 回答
1

我脑海中浮现的唯一想法是你在幕后有一个错综复杂的逻辑。我认为以sendNotification某种方式递归调用addToReplayHistory. 因此,多线程是一条红鲱鱼,日志文件显示只涉及一个线程,并且在 sendNotification 之后立即有 addToReplayHistory 调用,它修改了集合并破坏了交互器。

有关异常的更多信息在 javadoc 中:

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

于 2013-07-11T10:49:44.977 回答
0

要为kan 的答案添加一些细节:

synchronizedJava 中的块是可重入的:

可重入同步

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

所以就像 kan 指出的那样,实际上可能不是多个线程正在修改您的集合,而是只有一个线程,由于可重入行为,它可能总是获取锁。

您应该寻找修复异常的方法是同步块之间的递归调用,或对alarmsHistory.

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

于 2013-07-12T10:08:52.647 回答