最近發(fā)現(xiàn)java 1.8的concurrentHashMap甸赃,在使用computeIfAbsent時,如果涉及修改map趟据,則會產(chǎn)生bug券犁。
示例代碼如下:
System.out.println("start.");
map.computeIfAbsent("t",
(String t) -> map.computeIfAbsent("t", (String i) -> "i")); //halt在這里
System.out.println("fin.");
如果執(zhí)行這段代碼,你會發(fā)現(xiàn)代碼會停在注釋出汹碱,一直沒有結(jié)果粘衬。
最開始以為是遞歸實現(xiàn)的問題,通俗的說咳促,就是在構造一個函數(shù)的時候陷入了自遞歸稚新。就是你想構造一個A,但是A的構造依賴A已完成構造后的某些屬性跪腹。為了驗證是否是這個原因褂删,我們把代碼做一些調(diào)整,消除遞歸調(diào)用冲茸。
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
System.out.println("start.");
map.computeIfAbsent("t",
(String t) -> {
map.put("t", "t");
return "t";
});
System.out.println("fin.");
你會發(fā)現(xiàn)屯阀,代碼繼續(xù)停在哪里缅帘,無法輸出"fin."。
然后懷疑是死鎖难衰,懷疑concurrentHashMap使用了非可重入鎖钦无。但是跟著看conrrentHashMap的實現(xiàn),發(fā)現(xiàn)是基于cas + synchronized的方式實現(xiàn)盖袭,而synchronized本身是可重入的失暂,因此這里不滿足死鎖的條件。
繼續(xù)看concurrentHashMap的注釋苍凛,里面有這樣一句話:
/*
must not attempt to update any other mappings of this map.
*/
這句話確定了這個問題應該是已知存在的趣席。
所以應該絕對避免在computeIfAbsent中有遞歸,或者修改map的任何操作醇蝴。
為了搞清楚原因宣肚,我們繼續(xù)debug concurrentHashMap的源碼,發(fā)現(xiàn)這種在computeIfAbsent中悠栓,如果嘗試修改map的情況下霉涨,代碼會在
for (Node<K,V>[] tab = table;;) { //無限循環(huán)
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & h)) == null) {
Node<K,V> r = new ReservationNode<K,V>();
synchronized (r) {
if (casTabAt(tab, i, null, r)) { //cas
....
中反復循環(huán)。
我嘗試通俗的解釋一下這個問題:
注:不見得正確惭适,只是個人理解
由于concurrentHashMap中使用的是cas操作笙瑟,因此在出現(xiàn)cas嵌套的情況下,就會形成一種『死鎖』癞志。舉例來說往枷,一個值原來是 1, 我想把它修改成2凄杯,正常的cas操作错洁,會比較在修改的那一刻,值是否仍然為1戒突。這種比較屯碴,在cas只有一層的情況下,是沒有問題的膊存。但是导而,假如有兩層cas,這個值原來是1隔崎,第一層把 1 -> 2今艺,在cas還沒有生效時,繼續(xù)進入第二層cas操作爵卒,把 2 -> 3虚缎,當最終提交時,第二層cas比較當前值是否是2技潘,但由于當前指仍然是1遥巴,因此修改無效。最終反復進入循環(huán)享幽,形成死鎖铲掐。
雖然computeIfAbsent的代碼注釋中對這種修改map的行為做了強提示,但在實際中值桩,我認為這種行為仍舊是concurrentHashMap的一個實現(xiàn)bug摆霉。
https://bugs.openjdk.java.net/browse/JDK-8172951
好在這個問題在java 1.9中已經(jīng)基本修復了。
This is fixed in JDK 9 with JDK-8071667 . When the test case is run in JDK 9-ea, it gives a ConcurrentModification Exception.
java 9