【问题标题】:WeakHashMap and Concurrent ModificationWeakHashMap 和并发修改
【发布时间】:2018-01-23 21:24:57
【问题描述】:

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

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

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

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

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

我错过了什么吗?

【问题讨论】:

标签: java multithreading concurrency garbage-collection


【解决方案1】:

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

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

【讨论】:

  • @tsolakp:无论哪个线程执行触发删除过时条目的操作。例如,如果线程 A 调用 size() 并且 size() 清除过时的条目以获得最新的大小,那么线程 A 将执行清除。
  • 谢谢。这正是我想听到的。删除密钥的不是 GC 线程,而是执行此操作的实际调用线程。也许你可以用你最后的评论来更新你的答案。
【解决方案2】:

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

因为垃圾收集器可能随时丢弃键,WeakHashMap 可能表现得好像一个未知线程正在默默地删除条目。

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

特别是,即使您在 WeakHashMap 实例上进行同步并且不调用它的任何 mutator 方法,size 方法也有可能随着时间的推移返回较小的值,isEmpty 方法返回 @987654325 @ 然后true,对于containsKey 方法返回true 和稍后的false 对于给定键,对于get 方法返回给定键的值但稍后返回null,对于put 方法返回 nullremove 方法返回 false 对于以前出现在映射中的键,以及对键集、值集合和条目集的连续检查依次产生较少数量的元素。

【讨论】:

  • 我认为问题在于,当 GC 线程删除“k1”键并且当前线程同时获得“k1”的值时,如何确保我们不会进入不一致状态,这一切都没有同步。
  • 在什么意义上不一致?
  • 例如,在 GC 将其从 Map 中删除后,“k1”仍会获得值。
  • 为什么会这样?
  • 不一致的意思是如果你的地图有冲突,GC回收k1,用户插入k2,两个键冲突,那么如果没有同步,k2很可能永远不会被插入,或已插入但无法访问
【解决方案3】:

参考

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

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

后面的几个例子,给定的例子也被引用:

containsKey 方法为给定的键返回 true 和 false

所以基本上,永远不要依赖WeakHashMap 的状态。但尽可能地使用它。因此,给定的示例应改写为

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

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

【讨论】:

    【解决方案4】:

    此类主要用于关键对象,其 equals 方法使用 == 运算符测试对象身份。一旦这样的键被丢弃,它就永远无法重新创建,因此以后不可能在 WeakHashMap 中查找该键并惊讶于它的条目已被删除。

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

    第二个例子也是如此:

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

    您保留引用k,因此相应的对象是可访问的并且不能被丢弃。

    但是

    这个类可以很好地与等于的关键对象一起工作 方法不基于对象标识,例如 String 实例。 但是,对于此类可重新创建的关键对象,自动删除 其键已被丢弃的 WeakHashMap 条目可能被证明是 令人困惑。

    【讨论】:

    • k 不必与 put 方法中用作键的引用相同。
    • 然后map.contains(k) 将返回false,一切都会好起来的:)
    • 对于第一种情况,如果您的哈希映射有冲突,那么 GC 可能会尝试删除键 k1,用户插入键 k2 都发生在同一个索引处。
    • 我们可以有两个键k1k2,它们是k1 != k2,它们仍然可以为map.contains(k1)map.contains(k2) 在仅map.put(k1, v) 之后生成true
    • @tsolakp 啊,我的错,我应该补充一点,我的答案仅适用于 equals() 基于身份检查的对象。已编辑答案,谢谢
    猜你喜欢
    • 2011-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-28
    • 2016-11-19
    • 2023-03-16
    相关资源
    最近更新 更多