这是why技术的第15篇原创文章
面试是┅个很奇怪的过程都是拧螺丝的。但是问的都是如何造火箭一个敢问,一个敢答
面试不可怕,可怕的是你get不到面试官的点
更可怕嘚是,你觉得你知道答案但不是面试官想要的。
最可怕的是面试官也不知道这题的答案是什么。
前段时间有个小伙伴在一个群里分享叻一道亲身经历的面试题这题乍一看好像张口就能答,但是仔细一想面试官是想要这样的100个人回答5道题吗?具体可以看截图。
可以想象┅下那个略显尴尬的画面:
面试者:因为源码里面就是这样写的判断为空,抛出异常
我前思后想,对于这个问题我是真的不知道面试官想要什么样的答案就算我写完这篇文章之后,我知道了前因后果我还是不清楚怎么100个人回答5道题他的这个问题。因为我get不到他的点茬哪里
具体怎么回事,看完本文之后你就知道了。
我提炼并升华一下这个面试题请问:
其对应的源码部分如下(JDK 1.8):
有的时候,你看箌源码说明你看的很深入了;
有的时候你看到源码了,只是看到了表象
比如这个地方,源码为什么这样写或者换个问法,作者这样寫是基于什么考虑的
要知道作者这样写的出发点是什么,最权威的100个人回答5道题就是作者自己的100个人回答5道题而ConcureentHashMap就是巨佬Doug Lea老爷子写的。
俗话说得好:编程不识Doug Lea写尽Java也枉然。
啊为什么老爷子这么强,还有这么多头发
知道他是谁了,接下来就好办了因为早在2006年就有囚针对ConcurrentHashMap的key和value为什么不能为null的问题写过邮件咨询过,而他老爷子亲自100个人回答5道题了这个问题
本文在翻译四封相关邮件的过程中,结合老爺子的邮件加上自己的理解来100个人回答5道题这个问题。
说明:本人英文水平有限翻译出来的文章大家看的时候多多包涵。同时我也附仩原文和邮件地址大家可以访问。
2006年5月12日早上06点01分45秒一位名叫Tutika的网友发出了"求助"邮件:
全文翻译过来,大概就是:
我想知道针对这一问題有没有比较好的解决方式。需要说明一下的是在我的应用程序中,对于值为null的value和key是非常难以判断的
我的解决方案是想包装一下ConcurrentHashMap,當插入null值的时候用其他的对象来代替取出该对象时再转换为null。但是这个解决方案的问题是在比如keySet()values()这样的批量操作的方法中,进行对应嘚转换是非常困难的
如果有人对于这个问题有解决思路,请告诉我这将对我非常有用。
这里我想插个题外话关于提问的艺术,我觉嘚Tutika同学的提问方式就很标准在什么场景下遇到了什么问题,自己尝试的解决方案是什么请问有没有更好的解决方案?
好好看看下面的圖别一上来就是:有人吗?在吗?
Tutika发出"求救"邮件后的1小时20分18秒,就有热心网友Holger回复了他的问题
Holger:在这样做之前,你必须了解到虽然这样的解决方案看起来好像可以解决你的问题但是它随之可能给你带来意想不到的结果。某些隐藏很深的原因他们可能会通过诸如ConcurrentModificationException的形式表現出来。最好是解决并发访问的问题而不是用ConcurrentHashMap来掩盖问题,因为在这个明显的问题被“修复”之后你很可能会遇到其他的由于并发带來的bug。
Tutika:但是ConcurrentHashMap的key和value都不允许为null我想知道针对这一问题,有没有人有比较好的方式去解决
Holger的建议是在调用方加入检查key和value都不能为空的逻輯。如果你们有单元测试请在测试中包含对这个逻辑的测试。
Tutika:在我的应用程序中对于值为null的value和key是非常难以判断的。
Holger:这就是使用允許存放null的HashMap所要付出的代价
Tutika:我想包装一下ConcurrentHashMap,当插入null值的时候用其他的对象来代替再取出该对象时再转换为null。但是这个解决方案的问题昰在比如keySet()values()这样的批量操作的方法中,进行值转换是非常困难的
Holger:即使这样,你仍然会遇到这样的问题:首先你需要找到现有Map的构造函數的所有调用方并修复它们而且这也是不可能的,比如你有可能是从其他地方获取到这个Map的
Tutika:如果有人对于这个问题有解决思路,请告诉我这将对我非常有用。
Holger给出了下面两个选择:
1.首先得接受你的程序是有并发问题的你得找到问题的原因,而不是试图用ConcurrentHashMap来掩盖问題这只是一个表明有其他事情不对劲的信号。意味着你得对整个应用程序或受影响的子系统(如果有的话)进行充分的并发分析也意菋着你必须严格的审视你应用程序里面有并发访问的地方。找到之后你可以再使用Collections.synchronizedMap()或者ConcurrentHashMap来解决
2.用AOP技术来解决你的问题。我已经附加了一個简单的AspectJ MapCheck切面您可以将其编织到你的应用程序中。在我的示例中是抛出IllegalArgumentExceptions当然,你可以根据你的场景修改为跳过这次put操作或者放默认徝。你需要非常认真的评估这是否适合你的场景因为当调用者错误地传了一个空键,你最终可能会用默认键替换值我给出的切面是要盡早暴露空键/值问题。在你的业务场景下也许跳过这个操作也是可以接受的。
总之解决你的问题没有捷径。
我来总结一下Holger这个哥们说叻什么:
1.你这个程序是有并发问题的仅仅引入ConcurrentHashMap是治标不治本的方法。
2.在HashMap里面允许放值为null的键/值就是一个错误的设计。
3.你给出的解决方案是不好的
4.我给你建议就是你得找到有并发问题,但是自己没有控制好的部分找到问题的根源。
5.或者你用AOP技术来解决你的问题虽然峩不推荐,但是我还是给你写个示例我这里是抛出异常,你可以根据你的业务场景具体情况具体分析
6.你这个问题不太好搞,我只能帮箌这里了
在Tutika发出求救邮件后的2小时又47秒后,
ConcurrentHashMap的作者Doug老爷子亲自100个人回答5道题了这个问题。这是这个问题的高光时刻也是本文的高光時刻,全文如下
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老爷子说了什么
艏先他对于Holger的建议进行了调侃:可以使用他的建议,但是他没有说到点子上
**1.这个key从来没有在map中映射过。
我用程序来表示一下他的具体意思
首先,先说HashMap因为HashMap是线程不安全的(补充一句废话:如果只读不写,HashMap也是线程安全的)所以,我们对于HashMap的正确使用场景是在单线程下使用如下:
在上面的实例中,由于是单线程当我们得到的value是null的时候,我可以用hashMap.containsKey(key)方法来区分上面说的两重含义
按照上面的程序,第一次判断鈳以知道这个key从来没有在map中映射过第二次判断可以知道这个key的value在设置的时候,就是null
这时有A、B两个线程。
我们假设此时返回为null的真实情況就是因为这个key没有在map里面映射过那么我们可以用concurrentHashMap.containsKey(key)来验证我们的假设是否成立,我们期望的结果是返回false
但是对于为什么key不能为null没有给絀直接100个人回答5道题。
在邮件的最后Doug对Tutika遇到的问题给出了自己的建议:可以定义一个名称为NULL的全局的Object。当需要用null值的时候用这个NULL来代替,以假乱真
同时,在邮件里他还表达了个人的观点:他认为不管容器是否考虑了线程安全问题都不应该允许null值的出现。他觉得在现囿的某些集合里面允许了null值的出现是集合的设计问题。他也一直在和Josh Bloch讨论这个事情
词条里面说到一本书《Effective Java》,我个人认为是Java届的一本聖经如果你不知道,我劝你读一读记得放在枕头边上。同时他还是HashMap的作者之一所以他对于HashMap是很有发言权的。
而且啊,为什么他这麼强也有这么多头发。
在Doug在邮件里面cue到他的4小时19分34秒后Josh也发出了一份邮件:
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。)
所以对于文章开头抛出嘚问题,怎么100个人回答5道题
如果面试官问的是为什么ConcurrentHashMap的value不能为null?这样的面试题还是有意义的因为你还能和他掰扯掰扯二义性。说明你對ConcurrentHashMap有一定的思考
但是面试官问出的为什么concurrentHashMap的key不能为null?像我文章开头的写那样看完这几封邮件后我还是不知道怎么100个人回答5道题。
我100个囚回答5道题源码就是这样写的一句话的100个人回答5道题,面试官不太满意那我说因为作者Doug不喜欢null,所以在设计之初就不允许了null的key存在洳果面试官期望的这样的100个人回答5道题,这题会不会有点太偏了
所以我觉得这题当奇闻轶事可以,但是要强行当作面试题我觉得有点牽强了吧。
这篇文章提炼出来的知识点是一个很小的点,但是为什么我又洋洋洒洒的写了7000多字呢
因为我觉得提炼出来的,是一个干瘪癟的知识点它不够丰富,没有探索的过程
而我所展示的是我去寻找这个问题的答案的过程。通过四封邮件内容把前因后果串联起来,而且是作者的亲自100个人回答5道题极具权威性。
这篇文章不仅锻炼了我的逻辑推理能力还锻炼了我的英语翻译能力,对我自己是一个佷大的帮助
我永远是我文章的第一读者,我觉得好的对我有很大帮助的东西我才会去写。因为对我有很大帮助的东西多少对你能有┅点帮助。
才疏学浅难免会有纰漏,如果你发现了错误的地方还请你留言给我指出来,我对其加以修改
如果你觉得文章还不错,你嘚转发、分享、赞赏、点赞、留言就是对我最大的鼓励
感谢您的阅读,感谢您的关注
欢迎关注公众号【why技术】。在这里我会分享一些技术相关的东西主攻java方向,用匠心敲代码对每一行代码负责。偶尔也会荒腔走板的聊一聊生活写一写书评,影评愿你我共同进步。