2015-06-27 19 views
27

Bitte lesen Sie die Frage sorgfältig durch, bevor Sie diese als Duplikat markieren.Methodenaufruf für Future.get() - Blöcke. Ist das wirklich wünschenswert?

Unten ist das Snippet des Pseudocodes. Meine Frage ist - Ist der folgende Code nicht die Vorstellung der parallelen asynchronen Verarbeitung nicht zu besiegen?

Der Grund, warum ich das frage, ist, weil der Haupt-Thread in dem folgenden Code eine Aufgabe übergeben würde, die in einem anderen Thread ausgeführt werden soll. Nachdem die Aufgabe in der Warteschlange übergeben wurde, blockiert sie die Future.get() - Methode, damit die Aufgabe den Wert zurückgibt. Ich würde lieber die Aufgabe im Hauptthread ausführen lassen, anstatt mich einem anderen Thread zu unterziehen und auf die Ergebnisse zu warten. Was habe ich gewonnen, indem ich die Aufgabe in einem neuen Thread ausgeführt habe?

Ich bin mir bewusst, dass Sie für eine begrenzte Zeit usw. warten könnten, aber was ist dann, wenn mir das Ergebnis wirklich wichtig ist? Das Problem wird schlimmer, wenn mehrere Aufgaben ausgeführt werden müssen. Es scheint mir, dass wir die Arbeit nur synchron machen. Ich kenne die Guava-Bibliothek, die eine nicht blockierende Hörerschnittstelle bietet. Aber ich bin interessiert zu wissen, ob mein Verständnis für die Future.get() API korrekt ist. Wenn es richtig ist, warum ist das Future.get() so konzipiert, dass es blockiert, wodurch der gesamte Prozess der parallelen Verarbeitung zunichte gemacht wird?

Hinweis - Für die Aufzeichnung verwende ich JAVA 6

public static void main(String[] args){ 

private ExectorService executorService = ... 

Future future = executorService.submit(new Callable(){ 
    public Object call() throws Exception { 
     System.out.println("Asynchronous Callable"); 
     return "Callable Result"; 
    } 
}); 

System.out.println("future.get() = " + future.get()); 
} 
+4

Die Idee ist, dass Sie mehrere Aufgaben einreichen würde und dann warten. Sie haben recht, wenn Sie zwischen den einzelnen Aufgaben auf das Ergebnis warten, werden sie seriell verarbeitet und Sie erhalten nichts. –

+0

@SkinnyJ Wenn mehrere Aufgaben eingereicht werden, woher wissen Sie, welches Ergebnis für welche Aufgabe zurückgegeben wird? Und wie warte ich auf mehrere Aufgaben? –

+1

@VishalP Sie haben dann eine Liste von Futures, die Sie mit isDone() überprüfen können oder holen Sie das Ergebnis mit get() – John

Antwort

28

Future bietet Ihnen Methode isDone(), die nicht blockiert und liefert true, wenn die Berechnung abgeschlossen ist, andernfalls false.

Future.get() wird verwendet, um das Ergebnis der Berechnung abzurufen.

Sie haben ein paar Optionen:

  • Anruf isDone() und wenn das Ergebnis für sie bereit ist fragen nach get() Aufruf darauf, wie es keine Blockierung
  • Block ist auf unbestimmte Zeit mit get()
  • Block für spezifizierte Zeitüberschreitung mit get(long timeout, TimeUnit unit)

Die ganze Future API Sache ist dort zu h Ein einfacher Weg, Werte von Threads zu erhalten, die parallele Aufgaben ausführen. Dies kann synchron oder asynchron erfolgen, wenn Sie dies bevorzugen, wie oben in den Aufzählungszeichen beschrieben.

UPDATE MIT CACHE Beispiel

Hier ist eine Cache-Implementierung von Java Concurrency in der Praxis, ein ausgezeichneter Anwendungsfall für Future.

  • Wenn die Berechnung bereits ausgeführt wird, wartet in Ergebnis der Berechnung interessieren Anrufer für die Berechnung
  • zu beenden Wenn das Ergebnis im Cache bereit ist, Anrufer wird es
  • sammeln, wenn das Ergebnis nicht bereit ist, und die Berechnung wurde noch nicht gestartet, der Aufrufer wird die Berechnung starten und das Ergebnis in Future für andere Aufrufer umbrechen.

Dies ist alles leicht mit Future API erreicht.

package net.jcip.examples; 

import java.util.concurrent.*; 
/** 
* Memoizer 
* <p/> 
* Final implementation of Memoizer 
* 
* @author Brian Goetz and Tim Peierls 
*/ 
public class Memoizer <A, V> implements Computable<A, V> { 
    private final ConcurrentMap<A, Future<V>> cache 
      = new ConcurrentHashMap<A, Future<V>>(); 
    private final Computable<A, V> c; 

public Memoizer(Computable<A, V> c) { 
    this.c = c; 
} 

public V compute(final A arg) throws InterruptedException { 
    while (true) { 

     Future<V> f = cache.get(arg); 
     // computation not started 
     if (f == null) { 
      Callable<V> eval = new Callable<V>() { 
       public V call() throws InterruptedException { 
        return c.compute(arg); 
       } 
      }; 

      FutureTask<V> ft = new FutureTask<V>(eval); 
      f = cache.putIfAbsent(arg, ft); 
      // start computation if it's not started in the meantime 
      if (f == null) { 
       f = ft; 
       ft.run(); 
      } 
     } 

     // get result if ready, otherwise block and wait 
     try { 
      return f.get(); 
     } catch (CancellationException e) { 
      cache.remove(arg, f); 
     } catch (ExecutionException e) { 
      throw LaunderThrowable.launderThrowable(e.getCause()); 
     } 
    } 
    } 
} 
+2

Aber dann wartest du immer noch in einer Schleife checkst du isDone(), blockiert das nicht auch? –

+1

Das hängt ganz von Ihrem Design ab. Du musst nicht. Zum Beispiel könnten Sie einen Thread haben, der Ereignisse in das System einleitet und auch regelmäßig nach Ergebnissen sucht. In diesem Szenario würde die Überprüfung auf Ergebnisse, wenn sie nicht bereit sind, nicht blockiert und Thread könnte mit dem Versenden von Ereignissen fortfahren. Es gibt viele Muster, die hier verwendet werden könnten, um ein Blockieren zu vermeiden. Überprüfen Sie das Observer-Muster, das zur Benachrichtigung über das Ergebnis verwendet werden kann, auch Active Object Pattern und Half Sync - Half Async, Publish Subscribe. Versuchen Sie, ein wenig über diese Muster zu lernen, um eine Idee zu bekommen, wie asynchrone Programmierung zu verwenden ist. – John

+1

Sie haben Recht. Aber ich frage mich immer noch, ob das unnötig kompliziert gemacht wird. Eine Out-of-the-Box-Lösung wäre sehr praktisch gewesen. –

1

Im Beispiel die Sie gegeben haben Sie könnten genauso gut alles in Ihrer main() Methode laufen und Ihre fröhliche Art und Weise gehen.

Aber nehmen wir an, Sie haben drei Berechnungsschritte, die Sie derzeit nacheinander ausführen. Nur zum Verständnis nehmen wir an, dass Schritt 1 t1 Sekunden dauert, Schritt2 dauert t2 Sekunden und Schritt3 dauert t3 Sekunden. Die gesamte Rechenzeit beträgt also t1+t2+t3. Nehmen wir an, dass t2>t1>=t3.

Jetzt betrachten wir ein Szenario, wenn wir diese drei Schritte parallel unter Verwendung Future ausgeführt, um jedes Rechenergebnis zu halten. Sie können überprüfen, ob jede Aufgabe mit einem nicht blockierenden isDone() Aufruf für entsprechende Futures ausgeführt wird. Was passiert jetzt? Theoretisch ist Ihre Ausführung so schnell wie t2 vervollständigt rechts? So haben wir haben einige Vorteile aus der Parallelität zu gewinnen.

Auch in Java8 gibt es CompletableFuture, die funktionale Rückrufe unterstützt.

+1

Lassen Sie mich Ihnen mein Beispiel geben. Die Aufgaben, die ich an den Dienst übergebe, führen zu einer Erhöhung von HTTP-Anfragen. Das Ergebnis der HTTP-Anfrage kann sehr lange dauern. Aber ich brauche das Ergebnis jeder HTTP-Anfrage. Die Aufgaben werden in einer Schleife übergeben. Wenn ich darauf warte, dass jede Aufgabe zurückkommt, dann verliere ich hier die Parallelität, nicht wahr? –

+0

Nein, rufen Sie nur auf, wenn Ihr isDone() 'true' zurückgibt. Das ist besser als alle Anrufe nacheinander auszuführen. Im Wesentlichen wird Ihre Ausführung nur so schnell sein wie der langsamste Webdienst (einschließlich Netzwerk usw.) –

0

Wenn Sie sich nicht um die Ergebnisse kümmern, dann spawnen Sie einen neuen Thread und von diesem Thread ExectorService API für die Aufgabe Vorlage. Auf diese Weise wird Ihr Elternteil Thread, d. H. main Thread in keiner Weise blockiert werden, würde es einfach einen neuen Thread spawnen und dann weitere Ausführung starten, während der neue Thread Ihre Aufgaben übermitteln wird.

Zum Erstellen eines neuen Threads - tun Sie es sich selbst, indem Sie eine ThreadFactory für Ihre asynchrone Thread-Erstellung oder verwenden Sie eine Implementierung von java.util.concurrent.Executor.

Wenn dies in einer JEE-Anwendung ist und Sie das Spring-Framework verwenden, können Sie problemlos einen neuen asynchronen Thread mit @async Annotation erstellen.

Hoffe, das hilft!

+0

@VishalP Haben Sie eine Antwort? – hagrawal

4

Unten ist der Ausschnitt des Pseudocodes. Meine Frage ist, ob der folgende Code nicht die Idee der parallelen asynchronen Verarbeitung besiegt?

Es hängt alles von Ihrem Anwendungsfall:

  1. Wenn Sie wirklich blockieren wollen, bis Sie das Ergebnis zu erhalten, verwendet Sperrung get()
  2. Wenn Sie für einen bestimmten Zeitraum warten können, um den Status zu kennen statt unendlich Sperrdauer, verwenden get() mit Time-out
  3. Wenn Sie sofort ohne Analyse des Ergebnisses fortsetzen und das Ergebnis bei zukünftigen Zeitpunkt prüfen, benutzen Sie CompletableFuture

    Eine Zukunft, die explizit abgeschlossen werden kann (Festlegen von Wert und Status) und als CompletionStage verwendet werden kann, um abhängige Funktionen und Aktionen zu unterstützen, die nach ihrer Fertigstellung ausgelöst werden.

  4. Sie können den Callback-Mechanismus von Runnable/Callable implementieren. Hier finden Sie aktuelle unten SE Frage:

    Java executors: how to be notified, without blocking, when a task completes?

+0

thnx @ravnidra. Ich habe damals nicht 1.8 benutzt, CompletableFuture kam also nicht in Frage. Aber die anderen Eingaben sind ziemlich relevant. –