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;
    }
    
相关推荐
h***066536 分钟前
java进阶知识点
java·开发语言
oMcLin37 分钟前
如何在 Debian 11 上配置并调优 Tomcat 应用服务器,支持高并发 Java 应用的流畅运行
java·tomcat·debian
什么都不会的Tristan41 分钟前
MybatisPlus-快速入门
java·开发语言
无心水41 分钟前
【分布式利器:腾讯TSF】2、腾讯微服务框架TSF实战指南:Spring Boot零侵入接入与容器化部署全流程
java·spring boot·分布式·微服务·springcloud·分布式利器·腾讯tsf
sxlishaobin1 小时前
设计模式之享元模式
java·设计模式·享元模式
黎明晓月1 小时前
Redis容器化(Docker)
java·redis·docker
Wpa.wk1 小时前
接口自动化测试 - REST-assure小练习(Java版-分层)
java·开发语言·经验分享·python·自动化·接口自动化·po
予枫的编程笔记1 小时前
深度解析Logstash与Beats:Elastic Stack数据采集处理双核心
java·elasticsearch·logstash·beats
qq_12498707531 小时前
基于Java的游泳馆管理系统(源码+论文+部署+安装)
java·开发语言·毕业设计·springboot·计算机毕业设计
m0_598177231 小时前
MYSQL开发- (1)
android·java·mysql