3 votes

Concurrence Java, sous quelle condition CompletableFuture.supplyAsync() retournera-t-il null ?

Un problème a été détecté dans l'environnement de production en ce qui concerne le CompletableFuture.supplyAsync() Nous disposons d'une méthode de traitement par lots comme ci-dessous :

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();
    }
}

Lorsqu'il fonctionne correctement, il s'imprime : [Terminer-tâche2, Terminer-tâche1].

Cependant, il arrive qu'une exception soit levée comme ci-dessous dans la production :

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)

J'ai enquêté sur les CompletableFuture.allOf() Le code source a permis de constater que si la liste avenirs contient null, par exemple, futures.add(null) l'exception sera levée, mais je ne sais vraiment pas dans quels scénarios l'exception sera levée. CompletableFuture.supplyAsync() en restApiCall retour de la méthode null ?

Je vous remercie de votre patience et de la lecture de ce long message.

5voto

Francesco Menzani Points 4100

futures est écrit par plusieurs threads, puisque vous consommez des stringList avec un flux parallèle. Cependant, les futures est un ArrayList ce qui n'est pas sûr pour les threads.

Par conséquent, vous ne pouvez pas être sûr que chaque élément ajouté à partir d'un autre thread sera visible sans une synchronisation appropriée. Lorsque vous le transformez en tableau, il y aura des problèmes de visibilité de la mémoire, qui sont indéterministes, ce qui explique pourquoi cela fonctionne parfois comme prévu.

Pour résoudre ce problème, on utilise normalement une collection concurrente. Cependant, dans ce cas, il n'est pas judicieux de paralléliser CompletableFuture.supplyAsync() puisqu'il s'agit d'un appel non bloquant. Par conséquent, la meilleure solution consiste à parcourir la liste en boucle :

stringList.forEach(str -> {

De même, le tableau pré-alloué dans toArray() doit être vide :

futures.toArray(new CompletableFuture[0])

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X