这道面试题我真不知道面试官想要的回答是什么(3)

简介: 这道面试题我真不知道面试官想要的回答是什么(3)

第三封:巨佬现身

邮件地址:http://cs.oswego.edu/pipermail/concurrency-interest/2006-May/002485.html

在Tutika发出求救邮件后的2小时又47秒后,

image.png

image.png

翻译一下:

Tutika:我想把我一个多线程的项目里面的一些HashMap用ConcurrentHashMap替换掉。在hashMap里面我可以放key或者value为null的数据,没有任何毛病。但是ConcurrentHashMap的key和value都不允许为null。

对于热心网友Holger的邮件,Doug说:你可以试着接受Holger的建议,虽然他都没有说到点子上...

对于Tutika提出的问题,Doug给出的回答是:在ConcurrentMaps (ConcurrentHashMaps, ConcurrentSkipListMaps)这些考虑并发安全的容器中不允许null值的出现的主要原因是他可能会在并发的情况下带来难以容忍的二义性。而在非并发安全的容器中,这样的问题刚好是可以解决的。在map容器里面,调用map.get(key)方法得到的值是null,那你无法判断这个key是在map里面没有映射过,还是这个key在map里面根本就不存在。这种情况下,在非并发安全的map中,你可以通过

map.contains(key)的方法来判断。但是在考虑并发安全的map中,在两次调用的过程中,这个值是有可能被改变的。

接下来Doug说了个题外话:我个人认为,在Maps或者Sets集合中允许null值的存在,就是公开邀请错误进入你的程序。而这些错误,只有在发生错误的情况下才能被发现。(我觉得在非并发安全的Maps和Sets中是否应该允许null的存在的这个问题,是关于集合的少数几个设计问题之一,这也Josh Bloch和我长期以来一直在争执的话题。)

Tutika:在我的整个应用程序中,对于值为null的value和key是非常难以判断的。

Doug给出的建议是:可以试一试在某个地方声明static final Object NULL=new Object(),然后用NULL替换掉所有用null的地方。

翻译结束。

我再来解析一下Doug老爷子说了什么。

首先他对于Holger的建议进行了调侃:可以使用他的建议,但是他没有说到点子上。

说主要原因时,Doug用了反证法,先假定ConcurrentHashMap也可以存放value为null的值。那不管是HashMap还是ConcurrentHashMap调用map.get(key)的时候,如果返回了null,那么这个null,都有两重含义:

1.这个key从来没有在map中映射过。

2.这个key的value在设置的时候,就是null。

他说在非线程安全的map集合(HashMap)中可以使用map.contains(key)方法来判断,而ConcurrentHashMap却不可以。

我用程序来表示一下他的具体意思。

首先,先说HashMap,因为HashMap是线程不安全的(补充一句废话:如果只读不写,HashMap也是线程安全的),所以,我们对于HashMap的正确使用场景是在单线程下使用。如下:

image.png

在上面的实例中,由于是单线程,当我们得到的value是null的时候,我可以用hashMap.containsKey(key)方法来区分上面说的两重含义。

按照上面的程序,第一次判断可以知道这个key从来没有在map中映射过。第二次判断可以知道这个key的value在设置的时候,就是null。

所以当map.get(key)返回的值是null,在HashMap中虽然存在二义性,但是结合containsKey方法可以避免二义性。

但是如果是ConcurrentHashMap呢?它的使用场景是多线程的情况下。我们还是用反证法来推理,假设concurrentHashMap允许存放值为null的value。

这时有A、B两个线程。

线程A调用concurrentHashMap.get(key)方法,返回为null,我们还是不知道这个null是没有映射的null还是存的值就是null。

我们假设此时返回为null的真实情况就是因为这个key没有在map里面映射过。那么我们可以用concurrentHashMap.containsKey(key)来验证我们的假设是否成立,我们期望的结果是返回false。

但是在我们调用concurrentHashMap.get(key)方法之后,containsKey方法之前,有一个线程B执行了concurrentHashMap.put(key,null)的操作。那么我们调用containsKey方法返回的就是true了。这就与我们的假设的真实情况不符合了。

这就是Doug说的在两次调用的过程中值是可能变化的(the map might have changed between calls.)。这就是Doug所要表达的二义性。

以上也是Doug对这个面试题(为什么ConcurrentHashMap中的value不允许为null)的回答。

但是对于为什么key不能为null没有给出直接回答。

在邮件的最后,Doug对Tutika遇到的问题给出了自己的建议:可以定义一个名称为NULL的全局的Object。当需要用null值的时候,用这个NULL来代替,以假乱真。

同时,在邮件里他还表达了个人的观点:他认为不管容器是否考虑了线程安全问题,都不应该允许null值的出现。他觉得在现有的某些集合里面允许了null值的出现,是集合的设计问题。他也一直在和Josh Bloch讨论这个事情。

那么这个Josh Bloch是何许人也?


image.png


image.png


词条里面说到一本书《Effective Java》,我个人认为是Java届的一本圣经。如果你不知道,我劝你读一读,记得放在枕头边上。同时他还是HashMap的作者之一,所以他对于HashMap是很有发言权的。


而且,啊,为什么他这么强,也有这么多头发。


第四封邮件:Josh回应


邮件地址:http://cs.oswego.edu/pipermail/concurrency-interest/2006-May/002486.html


在Doug在邮件里面cue到他的4小时19分34秒后,Josh也发出了一份邮件:


image.png


Josh的邮件里说:Doug,这些年来我已经站在你的立场了。Maps集合中允许值为null的key和在Sets中允许null元素可能真的是一个错误。但是对于是否应该允许值为null的value存在,这点我还在思考。


另外,Josh想说的是,Doug比他更加讨厌null。但是这些年来,他也发现null是一个非常令人头疼的问题。


我来解读一下Josh想要表达的观点:


1.Doug你错怪我了,你不应该用争执来形容我们之间的问题,对于你的观点我已经接受一半了,另外一半我还在思考。


2.Doug你是对的,null真的是一个让人头疼的存在。


也许,从Josh这里,我能获取到为什么concurrentHashMap的key不能为null。因为Doug讨厌null值,结合Doug自己说法,他觉得允许为null的设计是不合理的:(他这里写的nulls,我理解是key和value都不能为null。)


image.png


到底怎么答?


所以,对于文章开头抛出的问题,怎么回答?


如果面试官问的是为什么ConcurrentHashMap的value不能为null?这样的面试题还是有意义的,因为你还能和他掰扯掰扯二义性。说明你对ConcurrentHashMap有一定的思考。


但是面试官问出的为什么concurrentHashMap的key不能为null?像我文章开头的写那样,看完这几封邮件后我还是不知道怎么回答。

我能怎么回答?


我回答源码就是这样写的?一句话的回答,面试官不太满意。那我说因为作者Doug不喜欢null,所以在设计之初就不允许了null的key存在。如果面试官期望的这样的回答,这题会不会有点太偏了?


所以我觉得这题当奇闻轶事可以,但是要强行当作面试题,我觉得有点牵强了吧。


image.png


最后说一点


这篇文章,提炼出来的知识点是一个很小的点,但是为什么我又洋洋洒洒的写了7000多字呢?


因为我觉得提炼出来的,是一个干瘪瘪的知识点,它不够丰富,没有探索的过程。


而我所展示的是我去寻找这个问题的答案的过程。通过四封邮件内容,把前因后果串联起来,而且是作者的亲自回答,极具权威性。


这篇文章不仅锻炼了我的逻辑推理能力,还锻炼了我的英语翻译能力,对我自己是一个很大的帮助。


我永远是我文章的第一读者,我觉得好的,对我有很大帮助的东西我才会去写。因为对我有很大帮助的东西,多少对你能有一点帮助。


才疏学浅,难免会有纰漏,如果你发现了错误的地方,还请你留言给我指出来,我对其加以修改。


感谢您的阅读,感谢您的关注。

目录
相关文章
|
7月前
|
算法 Java 调度
《面试专题-----经典高频面试题收集四》解锁 Java 面试的关键:深度解析并发编程进阶篇高频经典面试题(第四篇)
《面试专题-----经典高频面试题收集四》解锁 Java 面试的关键:深度解析并发编程进阶篇高频经典面试题(第四篇)
79 0
|
7月前
|
存储 Java
java面试题大全带答案_面试题库_java面试宝典2018
java面试题大全带答案_面试题库_java面试宝典2018
|
7月前
|
SQL 前端开发 Java
2019史上最全java面试题题库大全800题含答案(面试宝典)(4)
2019史上最全java面试题题库大全800题含答案(面试宝典)
|
7月前
|
SQL Java 数据库连接
2019史上最全java面试题题库大全800题含答案(面试宝典)(2)
2019史上最全java面试题题库大全800题含答案(面试宝典)
|
7月前
|
存储 设计模式 Java
java实习生面试题_java基础面试_java面试题2018及答案_java面试题库
java实习生面试题_java基础面试_java面试题2018及答案_java面试题库
|
7月前
|
SQL 算法 安全
java面试宝典_java基础面试_2018java面试题_2019java最新面试题
java面试宝典_java基础面试_2018java面试题_2019java最新面试题
|
7月前
|
算法 安全 网络协议
java高级面试题_java面试题大全带答案_线程面试题_java面试宝典2019
java高级面试题_java面试题大全带答案_线程面试题_java面试宝典2019
|
7月前
|
安全 算法 Java
java线程面试题_2019java面试题库
java线程面试题_2019java面试题库
|
7月前
|
前端开发 Dubbo Java
spring面试题_spring mvc面试题_springboot面试题库
spring面试题_spring mvc面试题_springboot面试题库
|
7月前
|
算法 前端开发 Java
2019史上最全java面试题题库大全800题含答案(面试宝典)(3)
2019史上最全java面试题题库大全800题含答案(面试宝典)

热门文章

最新文章