java map bug_ConcurrentHashMap的一个bug

最近发现java 1.8的concurrentHashMap,在使用computeIfAbsent时,如果涉及修改map,则会产生bug。

示例代码如下:

System.out.println("start.");

map.computeIfAbsent("t",

(String t) -> map.computeIfAbsent("t", (String i) -> "i")); //halt在这里

System.out.println("fin.");

如果执行这段代码,你会发现代码会停在注释出,一直没有结果。

最开始以为是递归实现的问题,通俗的说,就是在构造一个函数的时候陷入了自递归。就是你想构造一个A,但是A的构造依赖A已完成构造后的某些属性。为了验证是否是这个原因,我们把代码做一些调整,消除递归调用。

ConcurrentHashMap map = new ConcurrentHashMap<>();

System.out.println("start.");

map.computeIfAbsent("t",

(String t) -> {

map.put("t", "t");

return "t";

});

System.out.println("fin.");

你会发现,代码继续停在哪里,无法输出"fin."。

然后怀疑是死锁,怀疑concurrentHashMap使用了非可重入锁。但是跟着看conrrentHashMap的实现,发现是基于cas + synchronized的方式实现,而synchronized本身是可重入的,因此这里不满足死锁的条件。

继续看concurrentHashMap的注释,里面有这样一句话:

/*

must not attempt to update any other mappings of this map.

*/

这句话确定了这个问题应该是已知存在的。

所以应该绝对避免在computeIfAbsent中有递归,或者修改map的任何操作。

为了搞清楚原因,我们继续debug concurrentHashMap的源码,发现这种在computeIfAbsent中,如果尝试修改map的情况下,代码会在

for (Node[] tab = table;;) { //无限循环

Node 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 r = new ReservationNode();

synchronized (r) {

if (casTabAt(tab, i, null, r)) { //cas

....

中反复循环。

我尝试通俗的解释一下这个问题:

注:不见得正确,只是个人理解

由于concurrentHashMap中使用的是cas操作,因此在出现cas嵌套的情况下,就会形成一种『死锁』。举例来说,一个值原来是 1, 我想把它修改成2,正常的cas操作,会比较在修改的那一刻,值是否仍然为1。这种比较,在cas只有一层的情况下,是没有问题的。但是,假如有两层cas,这个值原来是1,第一层把 1 -> 2,在cas还没有生效时,继续进入第二层cas操作,把 2 -> 3,当最终提交时,第二层cas比较当前值是否是2,但由于当前指仍然是1,因此修改无效。最终反复进入循环,形成死锁。

虽然computeIfAbsent的代码注释中对这种修改map的行为做了强提示,但在实际中,我认为这种行为仍旧是concurrentHashMap的一个实现bug。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>