2016-11-03 2 views
2

J'ai un problème lors de l'utilisation de CompletableFuture en java. J'ai 2 demandes de sélection qui sont remplies lors de la réception des réponses du serveur.Java CompletableFuture.complete() block

Dans le fil de connexion (FILET-1) (réacteur à l'utilisation), j'utilise:

if(hasException) { 
    selectFuture.completeExceptionally(new ClientException(errorCode)); 
} else { 
    System.out.println("Before complete future"); 
    selectFuture.complete(result); 
    System.out.println("After complete future"); 
} 

Et dans d'autres fils (FILET-2), j'utilise:

CompleteFuture.allOf(allSelect).whenComplete((aVoid, throwable) -> { 
    System.out.println("Receive all future"); 
    // Do sth here 
}); 

Ma situation est que le système imprime "Recevoir tout futur" mais THREAD-1 est bloqué lors de l'appel future.complete(result); Il ne peut pas sortir de cette commande. Si dans THREAD-2, j'utilise CompletableFuture.allOf(allOfSelect).get(), le THREAD-1 fonctionnera correctement. Mais en utilisant CompletableFuture.get() réduit les performances, donc je voudrais utiliser CompletableFuture.whenComplete().

Quelqu'un peut-il m'aider à expliquer la cause du blocage?

Merci!

Répondre

2

L'appel complete déclenche tous les dépendants CompletionStage s.

Donc, si vous avez déjà enregistré un BiConsumer avec whenComplete, le complete l'appellera dans son thread appelant. Dans votre cas, l'appel au complete reviendra lorsque le BiConsumer que vous avez passé à whenComplete se termine. Ceci est décrit dans les les class javadoc

possibilités fournies pour complétions dépendantes de méthodes non asynchrones peuvent être effectuée par le fil qui complète le courant CompletableFuture, ou par tout autre appelant d'un procédé d'achèvement.

(par un autre appelant est la situation inverse, où le thread appelant whenComplete s'appliquerait en fait le BiConsumersi la cible CompletableFuture avaient déjà été achevés.)

Voici un petit programme pour illustrer la comportement:

public static void main(String[] args) throws Exception { 
    CompletableFuture<String> future = new CompletableFuture<String>(); 
    future.whenComplete((r, t) -> { 
     System.out.println("before sleep, executed in thread " + Thread.currentThread()); 
     try { 
      Thread.sleep(5000); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
     System.out.println("after sleep, executed in thread " + Thread.currentThread()); 
    }); 

    System.out.println(Thread.currentThread()); 
    future.complete("completed"); 
    System.out.println("done"); 
} 

Ce imprimera

Thread[main,5,main] 
before sleep, executed in thread Thread[main,5,main] 
after sleep, executed in thread Thread[main,5,main] 
done 

montrant que le BiConsumer a été appliqué dans le thread principal, celui qui a appelé complete.

Vous pouvez utiliser whenCompleteAsync pour forcer l'exécution de BiConsumer dans un thread distinct.

[...] qui exécute l'action donnée en utilisant par défaut de facilité d'exécution asynchrone de cette étape lorsque cette étape complète.

Par exemple,

public static void main(String[] args) throws Exception { 
    CompletableFuture<String> future = new CompletableFuture<String>(); 
    CompletableFuture<?> done = future.whenCompleteAsync((r, t) -> { 
     System.out.println("before sleep, executed in thread " + Thread.currentThread()); 
     try { 
      Thread.sleep(5000); 
     } catch (InterruptedException e) { 
      e.printStackTrace(); 
     } 
     System.out.println("after sleep, executed in thread " + Thread.currentThread()); 
    }); 

    System.out.println(Thread.currentThread()); 
    future.complete("completed"); 
    System.out.println("done"); 
    done.get(); 
} 

imprimera

Thread[main,5,main] 
done 
before sleep, executed in thread Thread[ForkJoinPool.commonPool-worker-1,5,main] 
after sleep, executed in thread Thread[ForkJoinPool.commonPool-worker-1,5,main] 

montrant que le BiConsumer a été appliqué dans un thread séparé.

+0

Merci beaucoup. Votre explication est très claire sur le filetage. Lors de ma première utilisation de CompletableFuture, je l'utilise uniquement pour appeler une requête unique du client vers le serveur (comme SELECT, UPDATE, etc.), donc il fonctionne parfaitement comme mes attentes (le client attend la réponse et remplit le futur). Mais maintenant, j'ai un problème avec TRANSACTION parce qu'il a des étapes. Je veux renvoyer le futur immédiatement comme un CompletableFuture après que tout soit fait SELECT, mais le futur n'est rempli que lorsque d'autres exécutions (UPDATE, DELETE) ont des résultats. Pouvez-vous me proposer une solution pour traiter les choses en chaîne. Merci beaucoup! –

+0

Je veux faire les choses dans l'ordre comme: 1. SELECT -> CompletableFuture 2. PREPARE -> CompletableFuture (faire d'autres appels ceux retournent des CompletableFutures aussi). 3. COMMIT -> CompletableFuture et la transaction doit retourner immédiatement à l'appelant lorsque je l'enregistre. Merci beaucoup! –