【问题标题】:Java concurrency, under what condition will CompletableFuture.supplyAsync() return nullJava并发,CompletableFuture.supplyAsync()在什么情况下会返回null
【发布时间】:2020-11-21 06:23:08
【问题描述】:

在生产环境中发现关于CompletableFuture.supplyAsync()的问题 我们有一个批处理方法,如下所示:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

public class CompletableFutureProblem {
    public void batchOperation(){
        List<String> stringList = new ArrayList<>();
        stringList.add("task1");
        stringList.add("task2");
        List<CompletableFuture<String>> futures = new ArrayList<>();
        stringList.parallelStream().forEach(str -> {
            CompletableFuture<String> response = restApiCall(str);
            futures.add(response);
        });
        //futures.add(null);
        CompletableFuture<Void> result = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
        CompletableFuture<List<String>> convertedResult = result.thenApply(v ->
            futures.stream().map(CompletableFuture::join).collect(Collectors.toList())
        );
        try {
            List<String> finishedTask = convertedResult.get();
            System.out.println(finishedTask.toString());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

    public CompletableFuture<String> restApiCall(String str){
        return CompletableFuture.supplyAsync(() -> {
            return "Complete-" + str;
        });
    }

    public static void main(String[] args) {
        CompletableFutureProblem problem = new CompletableFutureProblem();
        problem.batchOperation();
    }
}

当一切正常时将打印: [完成任务2,完成任务1]

但是,有时它会在生产中引发如下异常:

Exception in thread "main" java.lang.NullPointerException
    at java.util.concurrent.CompletableFuture.andTree(CompletableFuture.java:1320)
    at java.util.concurrent.CompletableFuture.allOf(CompletableFuture.java:2238)
    at third.concurrent.CompletableFutureProblem.batchOperation(CompletableFutureProblem.java:20)
    at third.concurrent.CompletableFutureProblem.main(CompletableFutureProblem.java:40)

我查了CompletableFuture.allOf()源码发现如果列表futures包含null,例如futures.add(null),就会抛出异常,但我真的不知道restApiCall方法中的CompletableFuture.supplyAsync()在什么情况下会返回null

感谢您的耐心阅读这篇长文。

【问题讨论】:

    标签: java multithreading java-8 concurrency nullpointerexception


    【解决方案1】:

    futures 正在被多个线程写入,因为您正在使用并行流使用 stringList。但是futuresArrayList,它不是线程安全的。

    因此,如果没有适当的同步,您无法确保从不同线程添加到其中的每个元素都是可见的。当您将其转换为数组时,会出现内存可见性问题,这是不确定的,因此有时它会按预期工作。

    为了解决这个问题,通常会使用并发集合。但是在这种情况下,并行化CompletableFuture.supplyAsync() 没有意义,因为它是一个非阻塞调用。因此,最好的解决方案是遍历列表:

    stringList.forEach(str -> {
    

    另外,toArray() 中的预分配数组应该是空的:

    futures.toArray(new CompletableFuture[0])
    

    【讨论】:

    • 请问我们如何重现此异常并确保这是由线程可见性问题引起的。
    • @LiJing while (true) problem.batchOperation(); 会在一段时间后产生异常。修复后它将无限期地继续运行。
    猜你喜欢
    • 2020-02-07
    • 1970-01-01
    • 2021-08-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-24
    • 1970-01-01
    相关资源
    最近更新 更多