【问题标题】:Java 8 ConcurrentModificationException when doing any kind of iteration进行任何类型的迭代时出现 Java 8 ConcurrentModificationException
【发布时间】:2015-07-06 16:56:06
【问题描述】:

试图解决这个问题 2 周,但没有任何成功。 :X

当我进行任何类型的迭代时都会发生这种情况,但主要是在使用 #forEach 时。

我没有以任何方式修改列表,也没有修改元素,所以这对我来说似乎很尴尬。示例代码:

    Map<Season, List<Team>> map = fetcher.getTeamsIn(ids);

    Set<Team> toCreateTeams = new HashSet<>();
    Set<Team> toUpdateTeams = new HashSet<>();

    map.forEach((k, v) -> {
        toCreateTeams.addAll(v.stream().filter(t -> !persistedTeams.containsKey(t.getId())).collect(Collectors.toSet()));
        toUpdateTeams.addAll(v.stream().filter(t -> {
            Date latestPersistedUpdate = persistedTeams.get(t.getId());
            return latestPersistedUpdate != null && t.getLastUpdated().after(latestPersistedUpdate);
        }).collect(Collectors.toSet()));
    });

map 在#getTeamsIn 中被实例化为new HashMap&lt;&gt;();

试图在 eclipse 中打破异常,看看是否有线程在做一些疯狂的事情,但对我来说一切似乎都很正常。 在下面的图片中,在迭代 map 时抛出了异常。

我也开始出现其他一些非常奇怪的行为,比如永远卡在 lambda 表达式中。在这种情况下,在我看来 Eclipse 停止在表达式中(出于某种未知原因),就好像在该行中设置了一些断点。当我暂停执行并仅恢复有问题的线程时,流程就会恢复正常(直到下一个 lambda 表达式)或一些疯狂的 ConcurrentModificationException。

在我看来,整个事情就像是一些 Eclipse 疯狂的错误,但我真的不想重建我的环境,如果是这样的话。

我正在使用

Java(TM) SE Runtime Environment (build 1.8.0_45-b14)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)

在 Linux Mint 上。

谢谢!

-- 更新 1--

要明确一点:即使对于这个简单的示例,也会发生错误:

map.forEach((k, v) -> {
    System.out.println("Test" + k.getId());
});

一些可能很重要的随机信息:该异常仅在打印地图的最后一个元素后才爆炸!

-- 更新 2--

关于更新 1 中的随机信息,这真的不重要,因为出于性能原因(至少在 HashMap 和 ArrayList 中),ConcurrentModificationException 仅在迭代结束时通过比较数组的实际大小来检查具有预期大小的元素。

#getTeamsIn 方法的代码:

public Map<Season, List<Team>> getTeamsIn(List<Season> seasons) throws InterruptedException {
    final CountDownLatch latch = new CountDownLatch(seasons.size());
    Map<Season, List<Team>> teamsInSeason = new HashMap<>();
    for (Season s : seasons) {
        httpclient.execute(new HttpGet(String.format(URL, s.getId())),
                new Callback(latch) {

                    @Override
                    public void completed(final HttpResponse response) {
                        super.completed(response);
                        try {
                            teamsInSeason.put(s, new TeamsUnmarshaller().unmarshal(response.getEntity().getContent()));
                        }
                        catch (IllegalStateException | IOException e) {
                            // TODO Auto-generated catch block
                            System.out.println(e);
                        }
                    }

                });
    }
    latch.await();
    return teamsInSeason;
}

Callback 类只是 implements FutureCallback&lt;HttpResponse&gt;countDown() 以及所有回调方法(#cancelled、#completed 和 #failed)中的 latch

【问题讨论】:

  • 能否包含map的声明?
  • 你能试试map.keySet().stream().forEach()map.entrySet().stream().forEach() 看看是否有任何一组会导致同样的问题?
  • 是 fetcher 在后台运行获取内容,而其他内容正在打印?
  • @lhasadad,我在#getTeamsIn 中使用CountDownLatch 来等待Apache httpasyncclient 完成请求。但是 fetcher 没有在后台运行。
  • 我看不出你在哪里调用 countDown。您是否有可能以某种方式多次调用倒计时,导致 getTeamsIn 方法返回早期和将来的请求完成,从而将项目添加到地图?

标签: java foreach lambda concurrentmodification


【解决方案1】:

好的,刚刚发现问题。我在 #getTeamsIn 方法中被覆盖的 #completed 方法需要很长时间(感谢 JAXB)才能返回。由于countDown()(在super.completed(response) 中被调用)在teamsInSeason.put(s, new TeamsUnmarshaller().unmarshal(response.getEntity().getContent())); 之前,所以我们遇到了问题。

修复简单而丑陋:

@Override
public void completed(final HttpResponse response) {
    try {
        teamsInSeason.put(s, new TeamsUnmarshaller().unmarshal(response.getEntity().getContent()));
    }
    catch (IllegalStateException | IOException e) {
        // TODO Auto-generated catch block
        System.out.println(e);
    } finally {
        super.completed(response);
    }
}

我认为,奇怪的 Eclipse 行为(由于某种未知原因卡在某个假想的断点中),如果它持续存在,那将是另一个主题的问题。

感谢大家的帮助和时间!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-07-05
    • 2017-02-24
    • 1970-01-01
    • 2013-07-15
    • 2012-11-21
    • 1970-01-01
    • 2023-04-08
    相关资源
    最近更新 更多