先看這么一段代碼:
Map map =newHashMap<>();
map.computeIfAbsent("a",key -> {
? ? ? ?map.put("a","v2");
? ? ? ? return"v1";
});
這段代碼執(zhí)行以后"a"對(duì)應(yīng)的value到底是多少呢骨田?
答案是執(zhí)行這行代碼的線程cpu占用會(huì)到100%螃征,而且程序不退出。查看線程堆棧出現(xiàn)這樣的情況:
"main" #1 prio=5 os_prio=31 tid=0x00007f804f002000 nid=0x1703 runnable [0x0000700000218000]
java.lang.Thread.State: RUNNABLE
at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1069)
at java.util.concurrent.ConcurrentHashMap.put(ConcurrentHashMap.java:1006)
at com.wangqun.HelloComputeIfAbsent.lambda$main$0(HelloComputeIfAbsent.java:14)
at com.wangqun.HelloComputeIfAbsent$$Lambda$1/796533847.apply(Unknown Source)
at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)
- locked <0x000000076b448b10> (a java.util.concurrent.ConcurrentHashMap$ReservationNode)
at com.wangqun.HelloComputeIfAbsent.main(HelloComputeIfAbsent.java:13)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
可以看到put方法和computeIfAbsent方法同時(shí)卡在了一個(gè)ReservationNode對(duì)象上过牙。查看ConcurrentHashMap的源碼可以發(fā)現(xiàn),這種情況在bucket沒有初始化的時(shí)候會(huì)發(fā)生征绸,簡(jiǎn)單來說computeIfAbsent會(huì)在bucket為null的時(shí)候初始化一個(gè)ReservationNode來占位克锣,然后等待后面的計(jì)算結(jié)果出來,再替換當(dāng)前的占位對(duì)象律适,而putVal會(huì)synchorized這個(gè)對(duì)象辐烂,并根據(jù)其hash值的正負(fù)來進(jìn)行更新,遺憾的時(shí)ReservationNode的hash是-3捂贿,在putVal中沒有處理過這種情況纠修,然后就一直for循環(huán)處理了。
這其實(shí)是一種編程bug厂僧,computeIfAbsent在使用的時(shí)候扣草,計(jì)算value的過程中一定不能出現(xiàn)對(duì)map的修改操作,否則如果修改的key和computeIfAbsent的key分到同一個(gè)桶,而且那個(gè)bucket沒有被使用過辰妙,就會(huì)悲劇鹰祸。
如果非要在計(jì)算新值的過程中修改map,可以換一種方法來實(shí)現(xiàn)computeIfAbsent的功能:
V value = map.get(k);
if (value == null) {
? ? V newValue = computeValue(k); ?// 這里對(duì)computeValue(k)的重復(fù)調(diào)用不敏感
? ? value = map.putIfAbsent(k, newValue);
? ?if (value == null) {
? ? ? ? return newValue;
? ?}
? ?return value;
}
對(duì)于HashMap.computeIfAbsent密浑,這么調(diào)用則沒有這種問題出現(xiàn)蛙婴。