哈喽,大家好,今天来盘《Java面试突击》系列。
ConcurrentHashMap为什么不允许插入null值?
在Java语言中,ConcurrentHashMap和Hashtable这些线程安全的集合是不允许key或value插入null值的,而HashMap又允许key或value插入null值,这到底是为什么呢?
null值插入演示
首先给HashMap插入null值,实现代码如下:
以上程序的执行结果如下:
从上述结果可以看出,HashMap是允许key或value插入null值的。
接着我们使用同样的方式尝试给ConcurrentHashMap的key和value插入null值,实现代码如下:
编译阶段没有报错,执行以上程序,得到的结果如下:
从上述报错信息可以看出,使用ConcurrentHashMap是不能插入null值的,否者程序在运行期间就会报空指针异常。
PS:Hashtable使用与ConcurrentHashMap类似,这里就不再重复演示了。
ConcurrentHashMap源码分析
为了寻找报错的原因,我们尝试打开ConcurrentHashMap的源码一探究竟。
打开ConcurrentHashMap添加元素的方法put实现源码如下:
从上述源码可以看出,在添加方法的第一句就加了判断:如果key值为null或者是value值为null,就直接抛出异常NullPointerException空指针异常,这就是咱们前面程序报错的原因了。
探索最终原因
通过上面源码分析,我们似乎已经找到了ConcurrentHashMap不允许插入null值的原因,用一句话概括就是:乌龟的屁股“规定”!
然而,这个原因是不能说服面试官的,虽然源码是这样设计的,但我们要思考的是,这样设计背后更深层次的原因,为什么ConcurrentHashMap不允许插入null?而HashMap又允许插入null呢?
二义性问题
所谓的二义性问题是指含义不清或不明确。
我们假设ConcurrentHashMap允许插入null,那么此时就会有二义性问题,它的二义性含义有两个:
- 值没有在集合中,所以返回null。
- 值就是null,所以返回的就是它原本的null值。
可以看出这就是ConcurrentHashMap的二义性问题,那为什么HashMap就不怕二义性问题呢?
可证伪的HashMap
上面说到HashMap是不怕二义性问题的,为什么呢?
这是因为HashMap的设计是给单线程使用的,所以如果查询到了null值,我们可以通过hashMap.containsKey(key)的方法来区分这个null值到底是存入的null?还是压根不存在的null?这样二义性问题就得到了解决,所以HashMap不怕二义性问题。
不可证伪的ConcurrentHashMap
而ConcurrentHashMap就不一样了,因为ConcurrentHashMap使用的场景是多线程,所以它的情况更加复杂。
我们假设ConcurrentHashMap可以存入null值,有这样一个场景,现在有一个线程A调用了concurrentHashMap.containsKey(key),我们期望返回的结果是false,但在我们调用concurrentHashMap.containsKey(key)之后,未返回结果之前,线程B又调用了concurrentHashMap.put(key,null)存入了null值,那么线程A最终返回的结果就是true了,这个结果和我们之前预想的false完全不一样。
也就是说,多线程的状况非常复杂,我们没办法判断某一个时刻返回的null值,到底是值为null,还是压根就不存在,也就是二义性问题不可被证伪,所以ConcurrentHashMap才会在源码中这样设计,直接杜绝key或value为null的歧义问题。
ConcurrentHashMap设计者的回答
对于ConcurrentHashMap不允许插入null值的问题,有人问过ConcurrentHashMap的作者DougLea,以下是他回复的邮件内容:
以上信件的主要意思是,DougLea认为这样设计最主要的原因是:不容忍在并发场景下出现歧义!
总结
在Java语言中,HashMap这种单线程下使用的集合是可以设置null值的,而并发集合如ConcurrentHashMap或Hashtable是不允许给key或value设置null值的,这是JDK源码层面直接实现的,这样设计的目的主要是为了防止并发场景下的歧义问题。
好了,本期内容到这里就结束了。
是非审之于己,毁誉听之于人,得失安之于数。
公众号:Java面试真题解析