【问题标题】:Map concurrency - modification exception地图并发——修改异常
【发布时间】:2013-06-12 16:26:36
【问题描述】:

以下代码由线程池执行。首先,地图不是并发的,所以我已经改变了它,但我仍然在第二行得到修改异常。 为了使这段代码成为线程安全的,我应该进行哪些更改?

ConcurrentHashMap<String, Account> entriesOnFile = IniReaderHelper.load();
for (Map.Entry<String,Account> entryFromFile: entriesOnFile.entrySet())
{
    EntryWrapper wrapperFromEntriesFile = new EntryWrapper(entryFromFile.getValue());
    if (wrapperFromEntriesFile.getName().equals(entryName))
    {
        Tracer.info("Found matching entry from the entries file for +'" + entryName + "'");
        synchronized(this) 
        {
            context.put(RequestServices.ENTRY_WRAPPER, wrapperFromEntriesFile);
        }
        entry = wrapperFromEntriesFile;
        break;
    }               
}

更多信息: 下面是返回地图的 .load() 函数的代码:

static public ConcurrentHashMap<String, Account> load() throws Exception
{
    BufferedReader reader = null;
    accounts.clear();
    try
    {
        reader = new BufferedReader(new FileReader(getEntriesFile()));
        Account current = null;
        String accountName;
        String line;
        while ((line = reader.readLine()) != null)
        {
                ... do stuff here then adding entry to the amp
                accounts.put(accountName, current);
        }
    }
    finally
    {
        if (reader != null)
            reader.close();
    }
    return accounts;
}

这是堆栈跟踪:

error code [1] : java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextEntry(Unknown Source)
at java.util.HashMap$EntryIterator.next(Unknown Source)
at java.util.HashMap$EntryIterator.next(Unknown Source)
at .....call(AsyncCommand.java:78)

堆栈跟踪指向第二行('for' 循环),它写入“EntryIterator.next”,所以这是否意味着我应该更改:

for (Map.Entry<String,Account> entryFromFile: entriesOnFile.entrySet())

for (ConcurrentHashMap.Entry<String,Account> entryFromFile: entriesOnFile.entrySet())

这是“帐户”的声明

private static ConcurrentHashMap<String, Account> accounts = new ConcurrentHashMap<String, Account>();

【问题讨论】:

  • 您似乎没有在修改 entriesOnFile 映射,因此您没有向我们显示代码,或者您没有告诉我们您的异常实际上是什么。
  • 我不想修改地图。我从一个文件中读取了一些 Account 类型的条目到 entryOnFile 中。然后我检查地图中的每个条目并将其名称与 entryName 进行比较,如果匹配,我会做一些事情。我得到的例外是修改,它在第二行,“for”在哪里。我从我的一位客户那里得到了日志,我实际上无法在实验室中重新创建它,因为这部分代码是在极端情况下运行的。
  • 您应该发布堆栈跟踪!听起来好像还有其他事情发生,因为如果您不修改地图,那么您没有理由得到 ConcurrentModificationException。
  • 您的堆栈跟踪表明您仍在使用 HashMap 而不是 ConcurrentHashMap。如果您参考条目集的 api 定义“视图的迭代器是一个“弱一致”迭代器,它永远不会抛出 ConcurrentModificationException,并保证遍历元素,因为它们在构造迭代器时存在,并且可能(但不保证)反映施工后的任何修改。”。因此,如果您实际上使用的是并发哈希映射,则在遍历映射时应该不可能抛出此异常。
  • 发布accounts的声明。堆栈跟踪表明它是HashMap,而不是ConcurrentHashMap

标签: java collections concurrency


【解决方案1】:

据我所知,跨线程共享帐户映射毫无意义(您在每次调用 load() 时都在清除和重建映射,因此最直接的解决方法是删除共享静态映射(帐户)并让 load() 在每次调用时创建并返回一个新的 Map。

【讨论】:

  • 但是“加载”的意图可能更像是对单例的“刷新”,在这种情况下,设计的这方面可能是可以的
  • 这段代码只有在出现问题并且我们需要重新构建地图时才会运行,这就是为什么它是静态的,通常这段代码不会被读取。
  • 您可能希望将 Map 及其加载更多地封装在一个类中,并对重新加载应用并发控制,以确保线程不会相互踩踏。您可以使用 synchronized 关键字或显式 Lock 并可能使用时间戳来指示上次重新加载地图的时间。
【解决方案2】:

在我看来,从你目前所说的来看,地图是完全共享的。在让另一个线程改变帐户字段之前,您可以同步对 load 方法的访问并复制地图的内容。另一种选择是更改加载方法以接受它可以填充的地图,而不是让它改变一个共享的地图。

话虽如此,异常没有意义。 CHM 不返回 HashMap$EntryIterator,它返回一个 ConcurrentHashMap$EntryIterator 并且无法抛出您在此处描述的堆栈。我的猜测是您没有运行您发布的代码,而是错误地运行旧版本。尝试在调试器中运行,或添加日志语句以验证发布的代码是否实际运行。

【讨论】:

  • 不能因为这个: for (Map.Entry entryFromFile: entriesOnFile.entrySet()) 我不应该使用 ConcurrentHashMap.Entry 而不是 Map.entry 吗?这个异常并没有发生在我的实验室中,它发生在客户的身上,所以我无法真正测试任何东西。在我将 Map 移至并发之前,我开始相信客户使用了错误版本的代码。
  • 这基本上就是我要说的...查看 CHM 代码,它无法生成您提供的堆栈跟踪 -> 这不是正在运行的代码
猜你喜欢
  • 2016-11-19
  • 1970-01-01
  • 2013-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多