You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java17中CompletableFuture抛异常时get()与join()方法无限挂起问题

Java 11升级OpenJDK 17后CompletableFuture.get()无限阻塞问题分析

故障复现代码

import java.util.Objects;
import java.util.concurrent.CompletableFuture;

import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.exception.ExceptionUtils;

public class TestClass {

    public static void main(String[] args) {
        CompletableFuture<String> streamFuture = CompletableFuture.supplyAsync(() -> {
            throw MyException.wrapIfNeeded(new Exception("MyException"));
        });
        String result = null;
        try {
            result = streamFuture.get();
        } catch (Exception e) {
            System.out.println("Exception: " + ExceptionUtils.getMessage(e));
        }
        System.out.println("Result: " + Objects.toString(result));
    }

    static class MyException extends RuntimeException {

        private static final long serialVersionUID = 3349188601484197015L;

        public MyException(Throwable cause) {
            super(cause == null ? null : cause.getMessage(), cause);
        }

        public static MyException wrapIfNeeded(Throwable e) {
            return e instanceof MyException ? (MyException) e : new MyException(e);
        }

        @Override
        public String toString() {
            return ReflectionToStringBuilder.toString(this);
        }
    }
}

问题现象

调用streamFuture.get()时出现无限阻塞,深入排查JDK源码可定位到程序卡在java.util.concurrent.ForkJoinPool的unmanagedBlock(ManagedBlocker blocker)方法的do-while循环中,对应JDK源码如下:

/** ManagedBlock for external threads */
private static void unmanagedBlock(ManagedBlocker blocker)
    throws InterruptedException {
    if (blocker == null) throw new NullPointerException();
    do {} while (!blocker.isReleasable() && !blocker.block());
}

根因定位

问题出在自定义异常MyException重写的toString()方法:Java 11之后加强了反射访问限制,同时旧版org.apache.commons.lang的ReflectionToStringBuilder在反射遍历异常对象的所有字段时,会递归访问到Throwable内部持有的ForkJoinPool线程上下文引用,反射访问过程中触发了ForkJoinPool的状态检查死锁,最终导致unmanagedBlock的循环条件永远无法满足,出现无限挂起。

修复方案

  • 优先替换过时的org.apache.commons.lang依赖,升级为org.apache.commons.lang3的对应版本,新版工具类已经适配JDK 11+的反射访问规则。
  • 避免在异常的toString()方法中使用全量反射遍历字段:手动拼接需要输出的自定义字段,或直接使用父类RuntimeException的默认toString()实现即可解决阻塞问题。

内容的提问来源于stack exchange,提问作者Chris Ociepa

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 12:45:01