为什么ConcurrentHashMap不允许插入null值而HashMap可以?
文章目录
- 为什么ConcurrentHashMap不允许插入null值而HashMap可以?
- HashMap源码
- ConcurrentHashMap源码
- 为什么ConcurrentHashMap需要加空值校验呢?
- 二义性问题
- 测试代码
- 代码分析
- 测试结果
- 结果分析
- HashMap如何解决二义性问题
- ConcurrentHashMap为什么不能解决二义性问题
- HashMap作者Doug Lea的回答
- 小结
参考&鸣谢
为什么ConcurrentHashMap不允许插入null值? Tom弹架构
ConcurrentHashMap为什么key和value不能为null 幻想的绝望
在Java中,我们通常使用Map来存储键值对数据,而ConcurrentHashMap作为一个高效的线程安全的Map,其使用在并发场景下非常广泛。但是,ConcurrentHashMap有一个特殊的限制,那就是它不允许key或value为null,这与普通的HashMap有所不同。
那么,为什么ConcurrentHashMap会有这样的限制呢?
HashMap源码
612行:hash()方法计算了key的值
339行:当key为null时,计算出的hash值为0,value放置在第0个桶上
ConcurrentHashMap源码
1006行:没有像HashMap一样先计算hash
1011行:先进行了判断key和value是否为null
为什么ConcurrentHashMap需要加空值校验呢?
因为存在二义性问题且ConcurrentHashMap没法解决
二义性问题
测试代码
代码分析
22行:获取test的value
23行:containsKey判断是否有test
24行:增加test和null值
25行:再次获取test的value
26行:containsKey再次判断是否有test
测试结果
结果分析
get方法获取到的value的结果都为null。所以当我们用get方法获取到一个value为null的时候,这里会产生二义性:
- 可能没有test这个key
- 可能有test这个key,只不过value为null
HashMap如何解决二义性问题
containsKey方法的结果一个为false一个为true,可以通过这个方法来区分上面说道的二义性问题
ConcurrentHashMap为什么不能解决二义性问题
因为ConcurrentHashMap是线程安全的,一般使用在并发环境下,你一开始get方法获取到null之后,再去调用containsKey方法,没法确保get方法和containsKey方法之间,没有别的线程来捣乱,刚好把你要查询的对象设置了进去或者删除掉了。
HashMap作者Doug Lea的回答
对于 ConcurrentHashMap 不允许插入 null 值的问题,有人问过 ConcurrentHashMap 的作者 Doug Lea,以下是他回复的邮件内容:
The main reason that nulls aren’t allowed in ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps) is that ambiguities that may be just barely tolerable in non-concurrent maps can’t be accommodated. The main one is that if map.get(key) returns null, you can’t detect whether the key explicitly maps to null vs the key isn’t mapped. In a non-concurrent map, you can check this via map.contains(key),but in a concurrent one, the map might have changed between calls.
Further digressing: I personally think that allowing nulls in Maps (also Sets) is an open invitation for programs to contain errors that remain undetected until they break at just the wrong time. (Whether to allow nulls even in non-concurrent Maps/Sets is one of the few design issues surrounding Collections that Josh Bloch and I have long disagreed about.)
It is very difficult to check for null keys and values in my entire application .
Would it be easier to declare somewhere static final Object NULL = new Object(); and replace all use of nulls in uses of maps with NULL?
-Doug
以上信件的主要意思是,Doug Lea 认为这样设计最主要的原因是:不容忍在并发场景下出现歧义!
小结
HashMap和ConcurrentHashMap在处理null键和null值方面有一些不同的行为。
在HashMap中,允许使用null作为键和值。这因为HashMap使用一个特殊的逻辑来处理null键。当插入一个键值对时,HashMap会算键的哈希码,并将其存储在内部数组的相应位置上。如果键为null,则哈希码为0,HashMap将其存储在数组的第一个位置上。对于值为null情况,HashMap没有何限制,可以正常存储检索。
然而,在ConcurrentHashMap中,不允许使用null作键或值。这是因为ConcurrentHashMap是线程安全的它使用了一些复杂的机制来保证并访问的正确性。为了实现这种线程安全性,ConcurrentHashMap采了分段锁(Segment)的机制,将整个数据结构分成多个小的片段,每个片段都有自己的锁。这样可以提高并发性能,但也引入了一些限制。其中之就是不允许使用null作为键或值,因为在并发环境下,无法准确地判断某键或值是否为null从而导致可能的错误行为。
ConcurrentHashMap在源码中加入不允许插入 null (空) 值的设计,主要目的是为了防止并发场景下的歧义问题。