ConcurrentHashMap死循环问题

在Java8的ConcurrentHashMap的computeIfAbsent/putIfAbsent方法中嵌套调用,可能会出现死循环问题:

java 复制代码
public static void main(String[] args) throws ExecutionException, InterruptedException {
        Map<String, List<String>> map = new ConcurrentHashMap<>();
        System.out.println("main started");
        // 外层computeIfAbsent
        List<String> list = map.computeIfAbsent("a", key -> {
            System.out.println("out computeIfAbsent started");
            // 内层再次computeIfAbsent,且key和外层保持一致(只要hashcode一致都会有这个问题)
            List<String> innerList = map.computeIfAbsent("a", innerKey -> {
                System.out.println("inner computeIfAbsent started");
                List<String> results = new ArrayList<>();
                System.out.println("inner computeIfAbsent end");
                return results;
            });
            innerList.add("1");
            System.out.println("out computeIfAbsent end");
            return innerList;
        });
        list.add("2");
        System.out.println("main end");

}

java8执行执行会死锁一直等待:

复制代码
main started
out computeIfAbsent started

Process finished with exit code 130 (interrupted by signal 2:SIGINT)

Java9及之后会抛异常:

复制代码
main started
out computeIfAbsent started
Exception in thread "main" java.lang.IllegalStateException: Recursive update
	at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1760)
	at TestAviator.lambda$main$1(TestAviator.java:198)
	at java.base/java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1705)
	at TestAviator.main(TestAviator.java:196)

Java8源码解读:

java 复制代码
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)
                // 1. 外层computeIfAbsent的第一次for循环先初始化table;
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & h)) == null) {
                // 2. 外层computeIfAbsent的第二次for循环走到这里;
                // 2. 内层computeIfAbsent的for循环都不走到这里
                // 2. 对应的slot不为空,则new一个新的链表节点;
                // 注意这里初始化了一个类似"标兵节点",ReservationNode,其中hashcode==RESERVED < 0
                Node<K,V> r = new ReservationNode<K,V>();
                // synchronized同一个线程可重入
                synchronized (r) {
                    // cas: null -> r
                    if (casTabAt(tab, i, null, r)) {
                        binCount = 1;
                        Node<K,V> node = null;
                        try {
                            // 3. 第一次computeIfAbsent会从mappingFunction.apply走到内层computeIfAbsent
                            // 注意,此时slot中的头节点还没set,还是null
                            if ((val = mappingFunction.apply(key)) != null)
                               // 构造真实Node
                                node = new Node<K,V>(h, key, val, null);
                        } finally {
                            // 替换掉ReservationNode
                            setTabAt(tab, i, node);
                        }
                    }
                }
                if (binCount != 0)
                    break;
            }
           // 4. 设置fh值
            else if ((fh = f.hash) == MOVED)
                
                tab = helpTransfer(tab, f);
            else {
                // 5. 内层computeIfAbsent会走到这里
                boolean added = false;
                // synchronized同一个线程可重入
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        // 6. 这里一直为false,因为头节点还是ReservationNode,其hashcode字段(即fh)<0, 会一直for循环下去
                        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);
                            }
                        }
                        // ***** 这里的 else if 是java9之后加的,会直接抛异常 *****
                        // else if (f instanceof ReservationNode)
                        //    throw new IllegalStateException("Recursive update");
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (!added)
                        return val;
                    break;
                }
            }
        }
        if (val != null)
            addCount(1L, binCount);
        return val;
    }
    
相关推荐
开发者小天9 小时前
python中For Loop的用法
java·服务器·python
flushmeteor9 小时前
JDK源码-基础类-String
java·开发语言
毕设源码-钟学长9 小时前
【开题答辩全过程】以 基于ssm的空中停车场管理系统为例,包含答辩的问题和答案
java
不愿是过客9 小时前
java实战干货——长方法深递归
java
小北方城市网10 小时前
Redis 分布式锁高可用实现:从原理到生产级落地
java·前端·javascript·spring boot·redis·分布式·wpf
六义义11 小时前
java基础十二
java·数据结构·算法
毕设源码-钟学长12 小时前
【开题答辩全过程】以 基于SpringBoot的智能书城推荐系统的设计与实现为例,包含答辩的问题和答案
java·spring boot·后端
笨手笨脚の12 小时前
深入理解 Java 虚拟机-03 垃圾收集
java·jvm·垃圾回收·标记清除·标记复制·标记整理
莫问前路漫漫12 小时前
WinMerge v2.16.41 中文绿色版深度解析:文件对比与合并的全能工具
java·开发语言·python·jdk·ai编程
九皇叔叔13 小时前
【03】SpringBoot3 MybatisPlus BaseMapper 源码分析
java·开发语言·mybatis·mybatis plus