ConcurrentModificationException与ConcurrentHashMap

huangapple go评论57阅读模式
英文:

ConcurrentModificationException with ConcurrentHashMap

问题

I understand your request. Here's the translated content without the code:

我理解其中的概念,但认为使用ConcurrentHashMap而不是HashMap会修复它。因为ConcurrentHashMap可以保护不同线程对数据的并发读取和修改。

但我仍然看到异常。

以下是错误堆栈跟踪 -

	at java.util.HashMap$HashIterator.nextNode(HashMap.java:1445)
	at java.util.HashMap$EntryIterator.next(HashMap.java:1479)
	at java.util.HashMap$EntryIterator.next(HashMap.java:1477)
	at java.util.concurrent.ConcurrentHashMap.putAll(ConcurrentHashMap.java:1083)
	at SampleFile.prepareAccountInfo(SampleFile.java:114)

以下是我不清楚的几个问题 -

  1. 为什么异常仍然发生?
  2. 有没有办法通过单元测试来测试修复?
英文:

I understand the concept behind it but thought using ConcurrentHashMap instead of HashMap will fix it. Because ConcurrentHashMap protects from concurrent reading and modification by different threads.

But I still see the exception.

Here's the code snippet-

SampleFile.java

prepareInfo(RequestHelper.getSender(request), someVar, concurrentMap);
....
...
	private void prepareInfo(final Sender sender, final SomeVar someVar, final ConcurrentHashMap<String,
			Object> concurrentMap){
		final Info info = RequestHelper.getInfo(someVar);
		someVar.setInfo(info);
		if(sender != null){
			prepareProfileInfo(sender.getUserDetails(), info, concurrentMap);
			mapDetailsWithMap(sender.getDetails(), concurrentMap);
			if(sender.getSenderId() != null){
				concurrentMap.put("sender_id", sender.getSenderId());
			}
			concurrentMap.putAll(sender.getAdditionalProperties());
		}
	}

The error stacktrace is -

	at java.util.HashMap$HashIterator.nextNode(HashMap.java:1445)
	at java.util.HashMap$EntryIterator.next(HashMap.java:1479)
	at java.util.HashMap$EntryIterator.next(HashMap.java:1477)
	at java.util.concurrent.ConcurrentHashMap.putAll(ConcurrentHashMap.java:1083)
	at SampleFile.prepareAccountInfo(SampleFile.java:114)

Couple of questions I am not clear about -

  1. Why is the exception still happening?
  2. Is there a way we can test the fix via Unit Test?

答案1

得分: 6

这行代码检索具有 sender.getAdditionalProperties() 的 HashMap,然后迭代 HashMap,将每个项目添加到 concurrentMap

concurrentMap.putAll(sender.getAdditionalProperties());

如果在迭代运行时修改了 sender 中的 HashMap,将会引发 ConcurrentModificationException 异常。该异常表示“在我迭代它时,地图的结构发生了修改,所以现在我不知道该怎么办”。

为了允许对 sender 对象内部的地图进行并发修改和迭代,该地图应该是 ConcurrentHashMap

要测试修复,请执行以下操作:

Map<String, Object> map = sender.getAdditionalProperties();
map.put("foo", "bar");
Iterator<Map.Entry<String, Object>> iterator = map.entrySet().iterator();
// 哎呀 - 添加一个项目会使 HashMap 迭代器失效
map.put("bar", "baz");
// 为 HashMap 抛出异常
iterator.next();
英文:

This line retrieves a HashMap with sender.getAdditionalProperties() and then iterates on the HashMap, adding each item to concurrentMap:

        concurrentMap.putAll(sender.getAdditionalProperties());

If the HashMap within sender is modified while the iteration runs, you will get a ConcurrentModificationException. The exception means "the structure of the map was modified while I was iterating on it, so I don't know what to do now".

To allow concurrent modification and iteration on the map inside the sender object, that map should be a ConcurrentHashMap.

To test the fix, you can make a test that does the following:

Map&lt;String,Object&gt; map =  sender.getAdditionalProperties()
map.put(&quot;foo&quot;, &quot;bar&quot;);
Iterator&lt;Map.Entry&lt;String, Object&gt;&gt; iterator = map.entrySet().iterator();
// uh-oh - adding an item invalidates HashMap iterator
map.put(&quot;bar&quot;, &quot;baz&quot;);
// Throws exception for HashMap
iterator.next();

答案2

得分: 4

你犯了一个可以理解的常见错误。

你看到了 ConcurrentModificationException,然后你想:哦,显然是线程的问题。

但那是错误的。这个名称... 或许有点不幸。

ConcurrentModificationException 与线程毫无关系

CoModEx 只是表示发生了以下事件序列:

  1. 你从集合中创建了一个迭代器,例如通过调用 .iterator(),或者通过编写 for (var x : someCollection) 这样的循环来创建。
  2. 在创建迭代器之后的某个时刻,你以某种方式更改了底层集合。你清空了它,向其中添加了东西,更新了一个值,或者删除了某些东西。总之,进行了任何一种更改。
  3. 你对在步骤 1 中创建的迭代器执行任何操作,例如调用 .hasNext()next(),或者你的 for (var x : someCollection) 到达闭合括号或者你执行了 continue

然后,出现 ConcurrentModificationException

这里有一种简单的方法来演示它。注意这个应用程序完全是单线程的:

class ThisAsplodes {
    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("Okay");
        for (String elem : list) {
            list.add("Bye");
        }
    }
}

编译它,运行它,然后,哇啦。ConcurrentModificationException。列表被修改(添加了 "Bye"),然后在更改之前创建的迭代器上进行了迭代(闭合括号)。

更有趣的是:如果你尝试在普通的哈希映射和多个线程(一个线程创建迭代器,另一个修改它)上执行这个事件序列,那么可能会发生 CoModEx,但更常见的情况是出现各种奇怪的错误。例如,你刚刚添加的键似乎不在映射中。使用 ConcurrentHashMap,你可以可靠地得到 ConcurrentModificationException

从你的代码中不容易看出你在哪里这样做,但至少现在你知道要查找的地方。

英文:

You've made an understandable and very common mistake.

You saw ConcurrentModificationException, and you thought: Huh. Apparently, threads.

But that's wrong. The name is... unfortunate, perhaps.

ConcurrentModificationException has nothing whatsoever to do with threading.

CoModEx simply means that this sequence of events has occured:

  1. You have made an iterator out of a collection, e.g. by calling .iterator(), or by writing for (var x : someCollection) which makes one.
  2. At some point in time after you made that iterator, you change the underlying collection in some way. You cleared it, added something to it, updated a value, or removed something. any change at all.
  3. You do anything with the iterator made in step 1, such as calling .hasNext() or next() on it, or your for (var x : someCollection) gets to the closing brace / you run a continue.

Then, boom. CoModEx.

Here's a trivial way to do it. Note how this app is entirely singlethreaded:

class ThisAsplodes {
    public static void main(String[] args) {
        List&lt;String&gt; list = new ArrayList&lt;String&gt;();
        list.add(&quot;Okay&quot;);
        for (String elem : list) {
            list.add(&quot;Bye&quot;);
        }
    }
}

compile it, run it, and, voila. ConcurrentModificationException. The list is modified ("Bye" is added), and then iteration occurs on an iterator made prior to the change (the closing brace).

It gets even better: If you try to perform this sequence of events with a plain jane hashmap and multiple threads (one thread makes the iterator, another modifies it), then CoModEx could happen, but more usually things just break in weird ways. a key you just added appears not to be in the map, for example. With ConcurrentHashMap, you get the CoModEx reliably.

It is not obvious from your code where you're doing this, but at least now you know where to look.

huangapple
  • 本文由 发表于 2020年8月14日 01:50:25
  • 转载请务必保留本文链接:https://go.coder-hub.com/63400659.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定