为什么ConcurrentHashMap防止空键和值?


141

的JavaDoc这样ConcurrentHashMap说:

类似Hashtable但不同HashMap,这个类就不会允许null用作键或值。

我的问题:为什么?

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

我已经使用了很多HashMaps来存储数据。但是当更改为ConcurrentHashMapNullPointerExceptions 时,我遇到了几次麻烦。


1
我认为这是一个非常烦人的不一致之处。EnumMap也不允许null。显然没有禁止空键的技术限制。对于Map <K,V>,仅V型字段将提供对空键的支持(如果要区分空值和无值,则可能是另一个布尔字段)。

6
更好的问题是“为什么HashMap允许空键和空值?”。或者可能是“为什么Java允许null驻留所有类型?”,甚至可能是“为什么Java根本没有null?”。
Jed Wesley-Smith

Answers:


220

ConcurrentHashMap他自己的作者(Doug Lea)

ConcurrentMaps(ConcurrentHashMaps,ConcurrentSkipListMaps)不允许使用null的主要原因是,无法容纳在非并行映射中几乎无法容忍的歧义。最主要的是,如果map.get(key)return null,则无法检测到该键是否显式映射到null该键。在非并发映射中,您可以通过进行检查 map.contains(key),但是在并发映射中,两次调用之间的映射可能已更改。


7
谢谢,但是将null作为密钥呢?
AmitW

2
为什么不在Optional内部使用s作为值
benez 18/09/18

2
@benez Optional是Java 8的功能,该功能在那时还不可用(Java 5)。Optional实际上,您现在可以使用s了。
布鲁诺

@AmitW,我认为ans是相同的,即模棱两可。例如,假设一个线程将键设置为null并为其存储一个值。然后另一个线程将另一个键更改为null。当第二个线程尝试添加新值时,它将被替换。如果第二个线程试图获取值,它将获取另一个键的值,该键由第一个修改。应该避免这种情况。
德克斯特(Dexter)

44

我相信,至少在一定程度上,它可以使您合并containsKeyget成为一个通话。如果映射可以容纳null,则get由于该值没有键,或者仅因为该值为null ,所以无法判断是否返回null。

为什么会有问题呢?因为没有安全的方法自己做。采取以下代码:

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

由于m是并发映射,因此可以在containsKeyget调用之间删除键k ,从而导致此代码段返回表中从未存在过的null,而不是所需的null KeyNotPresentException

通常,您可以通过同步来解决该问题,但是使用并发映射当然是行不通的。因此,get必须更改for的签名,并且以向后兼容的方式进行签名的唯一方法是防止用户首先插入空值,并继续将其用作“找不到密钥”的占位符。


你可以的map.getOrDefault(key, NULL_MARKER)。如果是null,则值为null。如果返回NULL_MARKER,则不存在该值。
奥利夫(Oliv)'18

@Oliv仅从Java 8开始。此外,该类型可能没有明智的null标记。
爱丽丝·珀赛尔

@AlicePurcell,“但是使用并发映射当然是行不通的”-为什么,我可以类似地在并发版本上进行同步-所以想知道为什么它不起作用。你能详细说明一下吗
samshers,

@samshers并发映射上没有任何操作同步,因此您需要从外部同步所有调用,这时您不仅失去了拥有并发映射的所有性能优势,还为以后的维护者留下了陷阱自然会希望能够安全地访问并发映射而不进行同步。
爱丽丝·珀赛尔

@AlicePurcell,太好了。尽管在技术上可行,但这绝对是维护的噩梦,以后的任何用户都不会期望他们必须在并发版本上进行同步。
samshers,

4

Josh Bloch设计的HashMap; 道格·李(Doug Lea)设计ConcurrentHashMap。我希望那不是诽谤。实际上,我认为问题在于null通常需要包装,以便真正的null可以代表未初始化。如果客户代码要求空值,那么它可以支付包装空值本身的费用(非常小)。


2

您不能在null上同步。

编辑:这不是在这种情况下的确切原因。最初我以为可以锁定并发更新,或者使用对象监视器来检测是否已修改某些东西,但是在检查源代码时我似乎是错的-他们使用基于哈希的位掩码。

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


??映射的键和值没有完成同步。那是没有道理的。
TobiasMüller,2009年

还有其他类型的锁定。这就是使其“并发”的原因。为了做到这一点,它需要一个对象来挂。
Paul Tomblin,2009年

2
为什么内部没有专用的对象可用于同步空值?例如“私有对象NULL = new Object();”。我想我以前见过……
Marcel

您还说什么其他类型的锁定?
TobiasMüller2009年

实际上,现在,我查看了源代码gee.cs.oswego.edu/dl/classes/EDU/oswego/cs/dl/util/concurrent / ...,我对此感到严重怀疑。似乎它使用段锁定,而不是锁定单个项目。
Paul Tomblin,2009年

0

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


0

我猜想API文档的以下片段给出了一个很好的提示:“在依赖于其线程安全性而不依赖于其同步详细信息的程序中,此类可与Hashtable完全互操作。”

他们可能只是想使其与ConcurrentHashMap完全兼容/可互换Hashtable。并且因为Hashtable不允许空键和值。


2
为什么Hashtable不支持null?
Marcel

通过查看其代码,我看不出Hashtable不允许空值的明显原因。也许这只是创建类时的API决定?HashMap对内部的空值有一些特殊的处理,而Hashtable则没有。(它总是抛出NullPointerException。)
TobiasMüller2009年

-2

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


1
您是否考虑过争夺Javachampion?
BlackBishop

如果您希望键中具有类似null的行为,请使用Optional。
爱丽丝·珀赛尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.