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;
    }
    
相关推荐
今年下半年11 分钟前
【网站连通性检测】java项目telnet、curl命令的使用
java·curl·telnet
摇滚侠11 分钟前
《Spring Boot 3:高级与架构设计》第 2 章 IOC 容器的高级机制 编程式驱动 IOC 阅读笔记 7
java·spring boot·笔记
IT枫斗者枫哥15 分钟前
UPDATE影响0行,接口却返回成功:把版本冲突接回业务
java·后端
霸道流氓气质23 分钟前
AI应用成本优化完全指南:Token压缩、语义缓存与模型路由的Java生产级实战
java·人工智能·缓存
mmm.c31 分钟前
Redis 两级缓存 + Pub/Sub 广播:一次为抗高并发做的缓存改造
java
yexianglunbai31 分钟前
中间件(Middleware)详解:概念、作用与实战
java·开发语言·中间件
叶总没有会33 分钟前
OpenFeign
java·开发语言·springcloud·openfeign
小七在进步34 分钟前
类和对象(四)
java·javascript·ajax
白帽攻防录37 分钟前
SRC 挖洞:Apache Tomcat 加密拦截器绕过深度复盘,CVE-2026-34486 fail-open 一行代码怎么打穿集群通信
java·网络安全·tomcat·apache
后端LV41 分钟前
三级缓存的两个失效盲区,我用 binlog 和消费组广播补上了
java·架构