今天測試在發給我一段報錯日志后,根據日志定位到從ConcurrentHashMap 的緩存中get的時候,ConcurrentHashMap的底層拋出了空指針,當時感覺很奇怪為什么在get的時候產生空指針了呢?
模擬代碼:
ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap(); ........................................................ concurrentHashMap.get(傳入的參數);
這個地方出現空指針,難道是傳入的null 所以出現了空指針了,事實證明確實傳入了null,但是我記得hashmap 是可以傳入null 的呀? 為什么ConcurrentHashMap 卻不行呢?
相應的去驗證了一下,remove 方法發現也不能傳入null 進去。
一、異常分析
FATAL EXCEPTION: main
java.lang.NullPointerException
at java.util.concurrent.ConcurrentHashMap.remove(ConcurrentHashMap.java:921)
本以為是並發異常,后發現不對,因為ConcurrentHashMap已經使用繼承自ReentrantLock的Segment,保證了線程安全,Java如果有這么大的bug肯定早修復了。看了下源碼

發現自己大意了,在獲取hashCode時NPE(hashMap不會,因為它會對null key做一次處理)。ConcurrentHashMap不接受null key和null value這個常識一時疏忽了,調用的地方確實有可能傳入null。這樣在remove前進行null判斷即可解決。
在問題解決后,想到hashMap和linkedhashMap都允許null key和null value,treeMap不允許null key,但允許null value,而ConcurrentHashMap既不允許null key也不允許null value,why?
二、not allow null key and value
stackoverflow並結合了源碼分析了一下
(1) treeMap不允許null key是因為compare會導致NPE, 可通過以下代碼實現null key支持
SortedMap<Integer, Integer> map = new TreeMap<Integer, Integer>(new Comparator<Integer>() { @Override public int compare(Integer arg0, Integer arg1) { if (arg0 == null) { if (arg1 == null) return 0; return -1; } if (arg1 == null) return 1; return arg0.compareTo(arg1); } });
(2) ConcurrentHashMap不允許null key和null value,是因為ConcurrentHashMap的鎖機制
對於get(Object key)如果返回null,ConcurrentHashMap沒辦法判斷是key不存在還是value就是null。有人得問了那么hashmap呢, 為什么可以,hashmap我們可以通過下面代碼實現判斷
Map<String, String> map = Collections.synchronizedMap(new HashMap<String, String>()); String key = "aa", value; synchronized (map) { if (map.containsKey(key)) { value = map.get(key); } else { throw new Exception("key is not exist"); } }
使用Collections.synchronizedMap對hashmap加鎖,Collections.synchronizedMap的鎖是同步鎖,就是對象本身,所以synchronized (map)與Collections.synchronizedMap內鎖統一。而ConcurrentHashMap的並發控制是利用分離鎖實現的(16個重入鎖),在外部無法獲得鎖,自己也沒有提供函數進行判斷,所以無奈了。
那么有沒有辦法既保證map的並發安全同時又允許null key和null value呢,當然了,兩種方式
第一種實際上面已經展現了,通過Collections.synchronizedMap對hashmap加鎖即可。synchronizedMap保證了線程安全,hashmap又允許null key和null value。
不過Collections.synchronizedMap的並發性能自然比不上ConcurrentHashMap的分桶鎖機制,如果對性能要求較高
理論上也可以重寫ConcurrentHashMap添加一個函數支持上面代碼段的並發,但這個要求對ConcurrentHashMap的分離鎖相當熟悉
最后:
關於為什么這么設計:
https://laiqitech.com/125/
關於null的一些小知識:
http://www.importnew.com/14229.html
