java8的ConcurrentHashMap的死循环问题

使用java8运行如下代码:

ConcurrentHashMap<String,Integer> map=new ConcurrentHashMap<>(16);
map.computeIfAbsent("AaAa", key->map.computeIfAbsent("BBBB",key2->42));
System.out.println("success");


这段代码将不会执行到System.out.println("success");因为上面的操作造成了死循环,如果将“AaAa”或者“BBBB”改成其他的东西,比如cccc,代码就可以正常执行了。

是什么原因导致了这个问题呢,这就涉及到ConcurrentHashMap使用的线程安全策略了。

我们跟踪进computeIfAbsent方法内看下为何会卡住:

public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
    if (key == null || mappingFunction == null)
        throw new NullPointerException();
    int h = spread(key.hashCode());
    V val = null;
    int binCount = 0;
    for (Node<K,V>[] tab = table;;) {
        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)) {
                    binCount = 1;
                    Node<K,V> node = null;
                    try {
                        if ((val = mappingFunction.apply(key)) != null)
                            node = new Node<K,V>(h, key, val, null);
                    } finally {
                        setTabAt(tab, i, node);
                    }
                }
            }
            if (binCount != 0)
                break;
        }
        else if ((fh = f.hash) == MOVED)
            tab = helpTransfer(tab, f);
        else {
            boolean added = false;
            synchronized (f) {
                if (tabAt(tab, i) == f) {
                    if (fh >= 0) {
                        binCount = 1;
                        for (Node<K,V> e = f;; ++binCount) {
                            K ek; V ev;
                            if (e.hash == h &&
                                ((ek = e.key) == key ||
                                 (ek != null && key.equals(ek)))) {
                                val = e.val;
                                break;
                            }
                            Node<K,V> pred = e;
                            if ((e = e.next) == null) {
                                if ((val = mappingFunction.apply(key)) != null) {
                                    added = true;
                                    pred.next = new Node<K,V>(h, key, val, null);
                                }
                                break;
                            }
                        }
                    }
                    else if (f instanceof TreeBin) {
                        binCount = 2;
                        TreeBin<K,V> t = (TreeBin<K,V>)f;
                        TreeNode<K,V> r, p;
                        if ((r = t.root) != null &&
                            (p = r.findTreeNode(h, key, null)) != null)
                            val = p.val;
                        else if ((val = mappingFunction.apply(key)) != null) {
                            added = true;
                            t.putTreeVal(h, key, val);
                        }
                    }
                }
            }
            if (binCount != 0) {
                if (binCount >= TREEIFY_THRESHOLD)
                    treeifyBin(tab, i);
                if (!added)
                    return val;
                break;
            }
        }
    }
    if (val != null)
        addCount(1L, binCount);
    return val;
}


在第一次调用该方法的时候,代码会进入到这句:

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)) {
            binCount = 1;
            Node<K,V> node = null;
            try {
                if ((val = mappingFunction.apply(key)) != null)
                    node = new Node<K,V>(h, key, val, null);
            } finally {
                setTabAt(tab, i, node);
            }
        }
    }
    if (binCount != 0)
        break;
}

执行到    val = mappingFunction.apply(key)  其实就是执行   key->map.computeIfAbsent("BBBB",key2->42)  来获取到值,以便完成最终的数据插入。这就会导致重新进入到computeIfAbsent方法里面,这个时候的table在31索引(”AaAa“和”BBBB“计算出来单的索引都是31)位置上是有值的,它是一个ReservationNode<K,V>节点,它的作用相当于一个占位符,并没有其他的作用,它的key和value全为null。

因此第二次进入到computeIfAbsent方法内,由于synchronized是可重入锁,所以代码并不会阻塞,会执行到下面的代码里面去:

else {
    boolean added = false;
    synchronized (f) {
        if (tabAt(tab, i) == f) {
            if (fh >= 0) {
                binCount = 1;
                for (Node<K,V> e = f;; ++binCount) {
                    K ek; V ev;
                    if (e.hash == h &&
                        ((ek = e.key) == key ||
                         (ek != null && key.equals(ek)))) {
                        val = e.val;
                        break;
                    }
                    Node<K,V> pred = e;
                    if ((e = e.next) == null) {
                        if ((val = mappingFunction.apply(key)) != null) {
                            added = true;
                            pred.next = new Node<K,V>(h, key, val, null);
                        }
                        break;
                    }
                }
            }
            else if (f instanceof TreeBin) {
                binCount = 2;
                TreeBin<K,V> t = (TreeBin<K,V>)f;
                TreeNode<K,V> r, p;
                if ((r = t.root) != null &&
                    (p = r.findTreeNode(h, key, null)) != null)
                    val = p.val;
                else if ((val = mappingFunction.apply(key)) != null) {
                    added = true;
                    t.putTreeVal(h, key, val);
                }
            }
        }
    }
    if (binCount != 0) {
        if (binCount >= TREEIFY_THRESHOLD)
            treeifyBin(tab, i);
        if (!added)
            return val;
        break;
    }
}


这个循环唯一的推出条件是binCount!=0,这就要求

a)if (fh >= 0)
b)else if (f instanceof TreeBin)
这两个条件至少有一个成立,其中fh = f.hash,普通的节点,如果正在被transfer,也就是扩容操作,hash会被设置为-1,这个时候这个线程检测到hash为-1,会帮忙扩容。但这里的f为占位节点,它的默认hash为-3,这可以根据它的构造方法看到

static final class ReservationNode<K,V> extends Node<K,V> {
    ReservationNode() {
        super(RESERVED, null, null, null);
    }
 
    Node<K,V> find(int h, Object k) {
        return null;
    }
}


其中

static final int RESERVED  = -3;
所以第一个条件不会满足,第二个条件很明显也不会满足,那么这个循环将永远不会结束,于是线程就卡在这里了。

这个问题和锁无关,属于设计问题,由于线程安全使用的策略是CAS,也就是自旋操作,导致必须符合条件,才能正常退出循环,但是恰好因为占位节点的hash值被设计成了-3,所以这个循环无论无何也无法达到退出条件了。

因此,官方也不建议在存入数据的时候嵌入其他的存数据的操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值