ConcurrentHashMap的 JavaDoc 说:
ConcurrentHashMap
与 Hashtable类似,但与 HashMap不同的是,这个类 没有允许将 null用作键或值。
Hashtable
HashMap
null
我的问题是,为什么?
第二个问题: 为什么 Hashtable不允许 null?
我使用了很多 HashMaps 来存储数据。但是在转换到 ConcurrentHashMap时,由于 NullPointerException,我遇到了好几次麻烦。
ConcurrentHashMap 是线程安全的。我相信不允许空键和值是确保它是线程安全的一部分。
无法在 null 上进行同步。
编辑: 在这种情况下,这不是确切的原因。我最初认为对并发更新进行锁定或者使用对象监视器来检测某些内容是否被修改是一件很有趣的事情,但是在检查了 源代码之后,我发现我错了——它们使用基于散列位掩码的“段”进行锁定。
在这种情况下,我怀疑他们这样做是为了复制 Hashtable,我怀疑 Hashtable 这样做是因为在关系数据库世界中,null!= null,因此使用 null 作为键没有任何意义。
我想下面的 API 文档片段给出了一个很好的提示: “在依赖于线程安全但不依赖于同步细节的程序中,这个类与 Hashtable 完全可互操作。”
他们可能只是想让 ConcurrentHashMap与 Hashtable完全兼容/可互换。因为 Hashtable不允许空键和值。.
Josh Bloch 设计了 HashMap,Doug Lea 设计了 ConcurrentHashMap。希望这不是诽谤。实际上,我认为问题在于 null 通常需要包装,所以真正的 null 可以代表未初始化。如果客户端代码需要空值,那么它可以支付包装空值本身的成本(诚然很小)。
我相信,至少在某种程度上,它允许您将 containsKey和 get合并为一个调用。如果 map 可以保存 null,那么就无法判断 get是否返回 null,因为该值没有键,或者仅仅因为该值为 null。
containsKey
get
为什么会有问题呢? 因为没有一种安全的方法可以让你自己做到这一点:
if (m.containsKey(k)) { return m.get(k); } else { throw new KeyNotPresentException(); }
由于 m是一个并发映射,键 k 可能在 containsKey和 get调用之间被删除,导致这个代码片段返回一个从未出现在表中的 null,而不是所需的 KeyNotPresentException。
m
KeyNotPresentException
通常您可以通过同步来解决这个问题,但是使用并发映射当然是不可行的。因此,get的签名必须改变,唯一的方法是防止用户首先插入 null 值,并继续使用它作为“ key not found”的占位符。
来自 ConcurrentHashMap的作者本人(Doug Lea) :
ConcurrentMaps 中不允许 null 的主要原因 (ConcurrentHashMaps,ConcurrentSkipListMaps)是指 可能只是勉强可以忍受在非并发映射不能 主要的一点是,如果 map.get(key)返回 null, 无法检测键是否显式映射到 null,而键不是 在非并发映射中,您可以通过 但在并发的 map.contains(key)中,地图可能已经更改 电话之间。
map.get(key)
map.contains(key)
我不认为不允许空值是一个正确的选择。 在许多情况下,我们确实希望将具有 null 值的键放入并发映射中。但是,通过使用 ConcurrentHashMap,我们不能这样做。 我建议未来版本的 JDK 可以支持这一点。