java 使用带有 Maps 键集的流时出现 ConcurrentModificationException
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/32580801/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
ConcurrentModificationException when using stream with Maps key set
提问by jaskmar
I want to remove all items from someMap
which keys are not present in someList
. Take a look into my code:
我想删除someMap
.key 中不存在键的所有项目someList
。看看我的代码:
someMap.keySet().stream().filter(v -> !someList.contains(v)).forEach(someMap::remove);
I receive java.util.ConcurrentModificationException
. Why? Stream is not parallel. What is the most elegant way to do this?
我收到java.util.ConcurrentModificationException
。为什么?流不是并行的。什么是最优雅的方式来做到这一点?
回答by Tagir Valeev
@Eran already explainedhow to solve this problem better. I will explain why ConcurrentModificationException
occurs.
@Eran 已经解释了如何更好地解决这个问题。我将解释为什么ConcurrentModificationException
会发生。
The ConcurrentModificationException
occurs because you are modifying the stream source. Your Map
is likely to be HashMap
or TreeMap
or other non-concurrent map. Let's assume it's a HashMap
. Every stream is backed by Spliterator
. If spliterator has no IMMUTABLE
and CONCURRENT
characteristics, then, as documentation says:
ConcurrentModificationException
发生这种情况是因为您正在修改流源。您Map
很可能是HashMap
或TreeMap
或 其他非并发映射。让我们假设它是一个HashMap
. 每个流都由Spliterator
. 如果 spliterator 没有IMMUTABLE
和CONCURRENT
特性,那么,正如文档所说:
After binding a Spliterator should, on a best-effort basis, throw
ConcurrentModificationException
if structural interference is detected. Spliterators that do this are called fail-fast.
绑定 Spliterator 后,
ConcurrentModificationException
如果检测到结构干扰,应尽最大努力抛出。这样做的 Spliterator 被称为fail-fast。
So the HashMap.keySet().spliterator()
is not IMMUTABLE
(because this Set
can be modified) and not CONCURRENT
(concurrent updates are unsafe for HashMap
). So it just detects the concurrent changes and throws a ConcurrentModificationException
as spliterator documentation prescribes.
所以HashMap.keySet().spliterator()
不是IMMUTABLE
(因为这Set
可以修改)和不是CONCURRENT
(并发更新对 来说是不安全的HashMap
)。所以它只是检测并发更改并ConcurrentModificationException
按照拆分器文档的规定抛出一个。
Also it worth citing the HashMap
documentation:
还值得引用HashMap
文档:
The iterators returned by all of this class's "collection view methods" are fail-fast: if the map is structurally modified at any time after the iterator is created, in any way except through the iterator's own remove method, the iterator will throw a
ConcurrentModificationException
. Thus, in the face of concurrent modification, the iterator fails quickly and cleanly, rather than risking arbitrary, non-deterministic behavior at an undetermined time in the future.Note that the fail-fast behavior of an iterator cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast iterators throw
ConcurrentModificationException
on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: the fail-fast behavior of iterators should be used only to detect bugs.
此类的所有“集合视图方法”返回的迭代器都是快速失败的:如果在迭代器创建后的任何时间对映射进行结构修改,除了通过迭代器自己的 remove 方法外,迭代器将抛出一个
ConcurrentModificationException
. 因此,面对并发修改,迭代器快速而干净地失败,而不是冒着在未来不确定的时间出现任意、非确定性行为的风险。请注意,无法保证迭代器的快速失败行为,因为一般而言,在存在非同步并发修改的情况下不可能做出任何硬保证。快速失败迭代器
ConcurrentModificationException
在尽力而为的基础上抛出。因此,编写一个依赖此异常来保证其正确性的程序是错误的:迭代器的快速失败行为应该仅用于检测错误。
While it says about iterators only, I believe it's the same for spliterators.
虽然它只涉及迭代器,但我相信拆分器也是如此。
回答by Eran
You don't need the Stream
API for that. Use retainAll
on the keySet
. Any changes on the Set
returned by keySet()
are reflected in the original Map
.
您不需要Stream
API。使用retainAll
上keySet
。对Set
返回的任何更改keySet()
都反映在原始Map
.
someMap.keySet().retainAll(someList);
回答by thecoop
Your stream call is (logically) doing the same as:
您的流调用(逻辑上)与以下内容相同:
for (K k : someMap.keySet()) {
if (!someList.contains(k)) {
someMap.remove(k);
}
}
If you run this, you will find it throws ConcurrentModificationException
, because it is modifying the map at the same time as you're iterating over it. If you have a look at the docs, you'll notice the following:
如果你运行它,你会发现它 throws ConcurrentModificationException
,因为它在你迭代它的同时修改地图。如果您查看docs,您会注意到以下内容:
Note that this exception does not always indicate that an object has been concurrently modified by a different thread. If a single thread issues a sequence of method invocations that violates the contract of an object, the object may throw this exception. For example, if a thread modifies a collection directly while it is iterating over the collection with a fail-fast iterator, the iterator will throw this exception.
请注意,此异常并不总是表示对象已被不同线程同时修改。如果单个线程发出一系列违反对象约定的方法调用,则该对象可能会抛出此异常。例如,如果线程在使用快速失败迭代器迭代集合时直接修改集合,则迭代器将抛出此异常。
This is what you are doing, the map implementation you're using evidently has fail-fast iterators, therefore this exception is being thrown.
这就是您正在做的事情,您正在使用的地图实现显然具有快速失败的迭代器,因此正在抛出此异常。
One possible alternative is to remove the items using the iterator directly:
一种可能的替代方法是直接使用迭代器删除项目:
for (Iterator<K> ks = someMap.keySet().iterator(); ks.hasNext(); ) {
K next = ks.next();
if (!someList.contains(k)) {
ks.remove();
}
}
回答by Ron McLeod
Later answer, but you could insert a collector into your pipeline so that forEach is operating on a Set which holds a copy of the keys:
稍后回答,但您可以在管道中插入一个收集器,以便 forEach 对包含键副本的 Set 进行操作:
someMap.keySet()
.stream()
.filter(v -> !someList.contains(v))
.collect(Collectors.toSet())
.forEach(someMap::remove);