5

在我从 Java 11 切换到 Java 17(从 Ubuntu 20.04 存储库安装 OpenJDK)后,以下代码不起作用:

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()- 它无限挂起。我挖得更深,发现java.util.concurrent.ForkJoinPool有一种方法unmanagedBlock(ManagedBlocker blocker)看起来像

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

并且程序在do-while循环中无限挂起。

编辑:我发现问题是由toString()添加到我的自定义异常类的方法引起的。出于某种原因,它在 Java 11 之后开始成为问题

4

2 回答 2

7

问题是由于ReflectionToStringBuilder.toString使用了封装的 java API。

我们可以尝试类似MyException.wrapIfNeeded(new NullPointerException("")).toString() And will see

线程“主”java.lang.reflect.InaccessibleObjectException 中的异常:无法使字段静态最终长 java.lang.RuntimeException.serialVersionUID 可访问:模块 java.base 不会“打开 java.lang”到未命名模块 @b4c966a

为什么这个原因会无限挂起?

CompletableFuture.AsyncSupply#run方法

    static final class AsyncSupply<T> extends ForkJoinTask<Void>
        implements Runnable, AsynchronousCompletionTask {
    ...
        public void run() {
            CompletableFuture<T> d; Supplier<? extends T> f;
            if ((d = dep) != null && (f = fn) != null) {
                dep = null; fn = null;
                if (d.result == null) {
                    try {
                        d.completeValue(f.get());
                    } catch (Throwable ex) {
                        d.completeThrowable(ex);
                    }
                }
                d.postComplete();
            }
        }

由于MyException被抛出,d.completeThrowable(ex);被调用,在这个方法内部,它会将异常包装到CompletionException,这将调用Throwablewith MyExceptionas的构造函数cause

    public Throwable(Throwable cause) {
        fillInStackTrace();
        detailMessage = (cause==null ? null : cause.toString());
        this.cause = cause;
    }

cause.toString()被调用时,它会抛出开头提到的异常,因此d.completeThrowable(ex);没有完全执行,因此程序将永远挂起。

于 2021-11-25T15:52:52.800 回答
1

我发现问题是由toString()添加到我的自定义异常类的方法引起的。出于某种原因,它(或者更准确地说:)ReflectionToStringBuilder.toString(this);在 Java 11 之后开始成为问题。

于 2021-11-25T15:31:39.330 回答