WeakHashMap和并发修改

问题描述 投票:2回答:4

我正在阅读有关WeakHashMap的Java Doc,我得到了基本概念。由于GC线程在后台运行,您可以获得“异常行为”,例如迭代时的ConcurrentModificationException等。

我没有得到的是,如果默认实现没有同步并且不以任何方式包含锁,那么为什么不可能获得不一致的状态。假设你有2个主题。 GC线程在某个索引处同时并在同一索引处删除某个键,用户线程在该数组中插入一个键值对。

对我来说,如果没有同步,那么获得不一致的哈希映射的风险很高。

更糟糕的是,做这样的事情实际上可能是非常危险的,因为v实际上可能是空的。

if (map.contains(k)) {
   V v = map.get(k)
}

我错过了什么吗?

java multithreading concurrency garbage-collection
4个回答
5
投票

您提到的不一致状态问题不会出现,因为GC不会主动重组WeakHashMaps。当垃圾收集器释放弱引用的引用时,相应的条目不会从映射中物理移除;条目只是陈旧,没有钥匙。稍后,可以在地图上的某些其他操作期间物理地移除该条目,但GC将不承担该责任。

您可以在grepcode上看到一个Java版本的此设计实现。


0
投票

您所描述的是文档明确指出的内容:

因为垃圾收集器可能随时丢弃密钥,所以WeakHashMap可能表现得好像未知线程正在静默删除条目。

你犯的唯一错误就是假设你可以通过同步来保护状态。这不起作用,因为GC的同步不会是相互的。引用文档:

特别是,即使你在WeakHashMap实例上同步并且没有调用它的mutator方法,size方法也有可能随着时间的推移返回更小的值,因为isEmpty方法返回false然后返回true,因为containsKey方法返回true和后来的false为给定的密钥,为get方法返回一个给定密钥的值,但后来返回null,为put方法返回nullremove方法返回false为一个以前似乎在映射,以及对密钥集,值集合和条目集的连续检查,以连续产生较少数量的元素。


0
投票

参考

即使您在WeakHashMap [...]上进行同步,size方法也可能会随着时间的推移返回较小的值

javadoc充分向我解释说,存在一种不一致状态的可能性,并且它完全独立于同步。

稍后举几个例子,也提到了给定的例子:

对于给定键,containsKey方法返回true,后来返回false

所以基本上,人们不应该依赖于WeakHashMap的状态。但尽可能使用原子。因此,应该将给定的例子改写为

V v = map.get(k);
if(null != v) {
}

要么

Optional.ofNullable(map.get(k)).ifPresent(() -> { } );

0
投票

此类主要用于密钥对象,其等于方法使用==运算符测试对象标识。一旦丢弃了这样的密钥,它就永远不会被重新创建,因此以后不可能在WeakHashMap中查找该密钥,并且对其条目已被删除感到惊讶。

因此,如果使用WeakHashMap作为equals()基于身份检查的对象,一切都很好。你提到的第一种情况(“一个GC线程在某个索引处删除某个键,同时在同一个索引处,用户线程在数组中插入一个键值对。”)是不可能的,因为只要用户线程保留对GC不能丢弃的密钥对象的引用。

第二个例子也是如此:

if (map.contains(k)) {
   V v = map.get(k)
}

保留引用k,以便可以访问相应的对象,并且不能将其丢弃。

这个类可以很好地处理其equals方法不基于对象标识的关键对象,例如String实例。但是,使用这种可重新调用的密钥对象,自动删除其密钥已被丢弃的WeakHashMap条目可能会令人困惑。

© www.soinside.com 2019 - 2024. All rights reserved.