【问题标题】:Why does ConcurrentHashMap prevent null keys and values?为什么 ConcurrentHashMap 会阻止 null 键和值?
【发布时间】:2010-10-16 11:11:02
【问题描述】:

ConcurrentHashMap 的 JavaDoc 是这样说的:

Hashtable 类似,但与HashMap 不同,此类不允许允许将null 用作键或值。

我的问题:为什么?

第二个问题:为什么Hashtable 不允许为空?

我使用了很多 HashMap 来存储数据。但是当更改为ConcurrentHashMap 时,由于 NullPointerExceptions,我遇到了几次麻烦。

【问题讨论】:

  • 我认为这是一个非常烦人的不一致。 EnumMap 也不允许为空。显然没有不允许空键的技术限制。对于 Map,只需一个 V 类型的字段即可支持空键(如果您想区分空值和无值,可能是另一个布尔字段)。
  • 一个更好的问题是“为什么 HashMap 允许空键和空值?”。或者可能,“为什么 Java 允许 null 存在于所有类型中?”,甚至“为什么 Java 有 null?”。

标签: java concurrenthashmap


【解决方案1】:

我不认为不允许空值是一个正确的选择。 在许多情况下,我们确实希望将具有空值的键放入并发映射中。但是,通过使用 ConcurrentHashMap,我们无法做到这一点。 我建议下一个版本的JDK可以支持。

【讨论】:

  • 你考虑过参加 Javachampion 吗?
  • 如果你想在你的键中使用类似 null 的行为,请使用 Optional。
【解决方案2】:

乔什·布洛赫设计的HashMap; Doug Lea 设计了ConcurrentHashMap。我希望这不是诽谤。实际上我认为问题在于空值通常需要包装,以便真正的空值可以代表未初始化。如果客户端代码需要空值,那么它可以支付(当然很小的)包装空值本身的成本。

【讨论】:

    【解决方案3】:

    From the author of ConcurrentHashMap himself (Doug Lea):

    ConcurrentMaps 中不允许空值的主要原因 (ConcurrentHashMaps, ConcurrentSkipListMaps) 在非并发映射中可能几乎无法容忍 容纳。主要的是如果map.get(key)返回null,你 无法检测密钥是否显式映射到 null 而密钥不是 映射。在非并发地图中,您可以通过以下方式检查 map.contains(key),但是在并发的时候,地图可能已经改变了 通话之间。

    【讨论】:

    • 谢谢,但是以 null 作为键呢?
    • 为什么不在内部使用Optionals 作为值
    • @benez Optional 是 Java 8 的一项功能,当时 (Java 5) 不可用。你现在确实可以使用Optionals。
    • @AmitW,我认为答案是相同的,即模棱两可。例如,假设一个线程将一个键设为 null 并针对它存储一个值。然后另一个线程将另一个键更改为空。当第二个线程尝试添加新值时,它将被替换。如果第二个线程试图获取值,它将获取不同键的值,即由第一个修改的键。这种情况应该避免。
    【解决方案4】:

    我相信至少在一定程度上允许您将containsKeyget 组合成一个调用。如果映射可以包含空值,则无法判断 get 是否返回空值是因为该值没有键,还是仅仅因为该值为空值。

    为什么会有这样的问题?因为自己没有安全的方法来做到这一点。取以下代码:

    if (m.containsKey(k)) {
       return m.get(k);
    } else {
       throw new KeyNotPresentException();
    }
    

    由于m 是一个并发映射,可能会在containsKeyget 调用之间删除键k,导致此sn-p 返回一个从未出现在表中的空值,而不是所需的@987654328 @。

    通常你会通过同步来解决这个问题,但是使用并发地图当然不会工作。因此get 的签名必须更改,并且以向后兼容的方式做到这一点的唯一方法是首先防止用户插入空值,并继续将其用作“未找到密钥”的占位符.

    【讨论】:

    • 你可以做map.getOrDefault(key, NULL_MARKER)。如果是null,则值为null。如果返回NULL_MARKER,则该值不存在。
    • @Oliv 仅从 Java 8 开始。此外,该类型可能没有合理的空标记。
    • @AlicePurcell,“但是使用并发映射当然不会工作” - 为什么,我可以类似地在并发版本上同步 - 所以想知道为什么它不会工作。你能详细说明一下吗?
    • @samshers 并发映射上没有操作同步,因此您需要在外部同步所有调用,此时您不仅失去了拥有并发映射的所有性能优势,而且您我们给未来的维护者留下了一个陷阱,他们自然希望能够在不同步的情况下安全地访问并发地图。
    • @AlicePurcell,太好了。尽管在技术上是可行的,但这绝对是一场维护噩梦,以后的任何用户都不会期望他们必须在并发版本上同步。
    【解决方案5】:

    您无法在 null 上同步。

    编辑:这并不是这种情况下的确切原因。我最初认为锁定事物以防止并发更新或以其他方式使用对象监视器来检测是否有修改,但在检查 the source code 时似乎我错了——他们使用基于“段”的“段”锁定哈希的位掩码。

    在那种情况下,我怀疑他们这样做是为了复制 Hashtable,我怀疑 Hashtable 这样做是因为在关系数据库世界中,null != null,所以使用 null 作为键没有任何意义。

    【讨论】:

    • 嗯?没有对 Map 的键和值进行同步。那没有任何意义。
    • 还有其他类型的锁定。这就是使它成为“并发”的原因。为了做到这一点,它需要一个对象来挂起。
    • 为什么内部没有可用于同步空值的特殊对象?例如“私有对象 NULL = 新对象();”。我想我以前见过这个......
    • 你指的还有哪些其他类型的锁定?
    • 实际上,现在我查看源代码gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent/… 我对此有严重的怀疑。它似乎使用分段锁定,而不是锁定单个项目。
    【解决方案6】:

    我想 API 文档的以下 sn-p 给出了一个很好的提示: "在依赖线程安全但不依赖同步细节的程序中,此类与 Hashtable 完全可互操作。"

    他们可能只是想让ConcurrentHashMapHashtable 完全兼容/可互换。由于Hashtable 不允许空键和值..

    【讨论】:

    • 为什么Hashtable不支持null?
    • 通过查看它的代码,我看不出 Hashtable 不允许空值的明显原因。也许这只是创建类时的 API 决定?! HashMap 在内部对 null-case 有一些特殊处理,而 Hashtable 没有。 (它总是抛出 NullPointerException。)
    【解决方案7】:

    ConcurrentHashMap 是线程安全的。我相信不允许空键和值是确保它是线程安全的一部分。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-08-14
      • 2023-04-05
      • 1970-01-01
      • 2013-02-28
      • 2013-10-16
      相关资源
      最近更新 更多