CompletableFuture 调用 uniApply 时出现 NullPointerException

Lio*_*fon 8 java asynchronous nullpointerexception java-8 completable-future

我收到这个异常:

Caused by: java.lang.NullPointerException
at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:616) ~[?:1.8.0_302]
at java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:591) ~[?:1.8.0_302]
at java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:488) ~[?:1.8.0_302]
at java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:1975) ~[?:1.8.0_302]
at com.tcom.concurrent.ConcurrentUtils$3.onSuccess(ConcurrentUtils.java:140) ~[framework-20220815.38-RELEASE.jar:?]
Run Code Online (Sandbox Code Playgroud)

这种异常很少见,并且在我的系统中无法重现。

查看 CompletableFuture.java 似乎fFunction 变量为 null。

但第二行有一个空检查uniApply(),所以f不能为空。

那么为什么 JVM 声称我在函数调用行上有 NPE 呢?如果 NPE 来自调用的函数内部,我不应该在堆栈跟踪中看到它吗?


ConcurrentUtils.java 中的相关部分:

public static <I, O> CompletableFuture<O> buildCompletableFuture(final ListenableFuture<I> listenableFuture,
                                                                 final Function<I, O> responseApplier,
                                                                 final Consumer<I> onSuccessConsumer,
                                                                 final Consumer<Throwable> onFailureConsumer,
                                                                 final Supplier<O> defaultValueOnExceptionSupplier,
                                                                 final Executor callBackExecutor) {
    //create an instance of CompletableFuture
    final CompletableFuture<I> innerComplete = new CompletableFuture<I>() {
        @Override
        public boolean cancel(boolean mayInterruptIfRunning) {
            // propagate cancel to the listenable future
            boolean result = listenableFuture.cancel(mayInterruptIfRunning);
            super.cancel(mayInterruptIfRunning);
            return result;
        }
    };

    // add callback
    Futures.addCallback(listenableFuture, new FutureCallback<I>() {
        @Override
        public void onSuccess(I result) {
            innerComplete.complete(result);
            if (onSuccessConsumer != null) {
                onSuccessConsumer.accept(result);
            }
        }

        @Override
        public void onFailure(Throwable t) {
            innerComplete.completeExceptionally(t);
            if (onFailureConsumer != null) {
                onFailureConsumer.accept(t);
            }
        }
    }, callBackExecutor);

    CompletableFuture<O> returnedFuture = innerComplete.thenApply(responseApplier);
    if (defaultValueOnExceptionSupplier != null) { //when we have default value the exception will not thrown
        returnedFuture = returnedFuture.exceptionally((ex) -> defaultValueOnExceptionSupplier.get());
    }

    return returnedFuture;
}
Run Code Online (Sandbox Code Playgroud)

CompletableFuture的相关代码:

final <S> boolean uniApply(CompletableFuture<S> a,
                           Function<? super S,? extends T> f,
                           UniApply<S,T> c) {
    Object r; Throwable x;
    if (a == null || (r = a.result) == null || f == null)
        return false;
    tryComplete: if (result == null) {
        if (r instanceof AltResult) {
            if ((x = ((AltResult)r).ex) != null) {
                completeThrowable(x, r);
                break tryComplete;
            }
            r = null;
        }
        try {
            if (c != null && !c.claim())
                return false;
            @SuppressWarnings("unchecked") S s = (S) r;
            completeValue(f.apply(s)); // This is CompletableFuture.java:616
        } catch (Throwable ex) {
            completeThrowable(ex);
        }
    }
    return true;
}
Run Code Online (Sandbox Code Playgroud)

以下是我调用 ConcurrentUtils.buildCompletableFuture 的方法:

ConcurrentUtils.buildCompletableFuture(asyncTask,
            ObjectWithTimestamp::getObject, s -> {},
            e -> logger.error("error message"), null, MoreExecutors.directExecutor())
Run Code Online (Sandbox Code Playgroud)

实际上是funiApply()ObjectWithTimestamp::getObject