Concurrency

Java 8: CompletableFuture in freier Wildbahn

Sven Ruppert
©shutterstock/svilen_mitkov

Java 8 gibt einem neue Werkzeuge im Bereich Concurrency an die Hand. Wir werden uns heute ansehen, wie man damit z. B. die beiden unterschiedlichen LifeCycles von CDI und JavaFX vereinbaren kann.

JavaFX und CDI sind ein mächtiges Paar. Es besteht nur die Herausforderung, die beiden LifeCycles bei der Initialisierung so zu synchronisieren, dass es nicht zu NullPointerExceptions oder Ähnlichem kommt. Dank Java 8 ist das nun kein Problem mehr.

Die Herausforderung

CDI als auch JavaFX haben in ihrem LifeCycle einen Punkt, den man als Entwickler zur Initialisierung verwenden kann. Bei JavaFX ist es die Methode initialize(..) und bei CDI die Methode, die mit der Annotation @Postconstruct versehen ist. Bei der Kombination JavaFX und CDI ist es aber leider so, dass, je nachdem wie die Initialisierung erfolgt, die Methoden in unterschiedlicher Reihenfolge aufgerufen werden. Was benötigt wird, ist ein Punkt, den man referenzieren kann, an dem beide Initialisierungen abgeschlossen sind. Aus Sicht des Entwicklers soll von der Problematik nichts mehr zu sehen sein. Um das Problem zu verdeutlichen, gehen wir im ersten Fall von der Erzeugung eines SimpleDateFormatters aus. Dieser benötigt ein Pattern, bevor die Methode format(..) aufgerufen wird.

private String pattern;
private SimpleDateFormat sdf;

//beispielhaft für eine init
public void createSDF(){
    this.sdf = new SimpleDateFormat(this.pattern);        
}

//beispielhaft für eine init
public void newPattern(final String pattern) {
    this.pattern = pattern;
}

public String format(final Date date){
    return sdf.fomat(date);
}

Beim Beispiel im Listing 1 ist eine zwingende Reihenfolge der Methodenaufrufe notwendig. In diesem Fall genau wie im Listing 2.

newPattern("yyyy.MM.dd");
createSDF();
final String s = versionAB.format(new Date());
System.out.println("s = " + s);

Nehmen wir an, dass die Methoden createSDF(..) und newPattern(..) jeweils eine der Initialiserungsmethoden von JavaFX und CDI sind. Wir können nicht sicher sein, dass die Methoden exakt in der Reihenfolge newPattern(..) und dann createSDF(..) aufgerufen werden. Es kann also vorkommen, dass erst createSDF(..) und dann erst newPattern(..) aufgerufen wird. In unserem Fall würde das zu einer Exception führen.

Die Implementierung

Wie kann man also erreichen, das Listing 3 ohne Fehler zu demselben Ergebnis wie Listing 2 führt?

createSDF();
newPattern("yyyy.MM.dd");

final String s = versionAB.format(new Date());
System.out.println("s = " + s);

Die Lösung beruht darauf, dass der Aufruf der Methode createSDF(..) nicht blockiert aber dennoch nach dem Aufruf der Methode newPattern(..) erfolgt.

Gehen wir davon aus, dass jede der Initialisierungsmethoden ein Boolean von false auf true setzt wenn die Methode ausgeführt worden ist. Hieraus ergeben sich zwei Boolean-Werte. Nennen wir sie initCompleteA und initCompleteB. Beide werden zu Beginn auf false gesetzt. Unkritisch ist das Setzen des Patterns, da es keine Vorbedingung benötigt (Listing 4).

public void newPattern(final String pattern) {
    this.pattern = pattern;
    initCompleteA=true;
    System.out.println("newPattern = " + pattern);
}

Das Erzeugen der Instanz von der Klasse SimpleDateFormat ist da schon ein wenig anfälliger, benötigt es doch die Ausführung der Methode newPattern(..) vorweg (Listing 5).

public void createSDF(){
    CompletableFuture<Void> supplyAsync
        = CompletableFuture
        .supplyAsync(taskCreateSDF, cachedThreadPool);
    supplyAsync.thenAccept(System.out::println);
}
public Supplier<Void> taskCreateSDF = ()-> {
    while(! initCompleteA ){
        try {
            System.out.println("createSDF is waiting" );
            TimeUnit.MILLISECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    sdf = new SimpleDateFormat(pattern);
    initCompleteB = true;
    return null;
};

Hier wird ein Supplier erzeugt, der dann dem ThreadPool übergeben wird. Da die Methode supplyAsync(..) verwendet wird, handelt es sich nicht um einen blockierenden Methodenaufruf. In diesem Beispiel wird alle 4 Millisekunden nachgesehen, ob die Vorbedingung nun erfüllt ist. Erst dann wird die Instanz erzeugt und das Attribut initCompleteB auf true gesetzt. Und zu guter Letzt soll die Methode format(..) erst ausgeführt werden, wenn beide Vorbedingungen erfüllt sind. Allerdings soll der Aufruf der Methode format(..) blockierend sein. Damit verhält sich das System ab der Stelle, wie es von den meisten Entwicklern erwartet wird (Listing 6).

public Supplier<String> task = ()-> {
//Warten bis alle true
    while(! (initCompleteA && initCompleteB) ){
        try {
            System.out.println("initCompleteA = " + initCompleteA);
            System.out.println("initCompleteB = " + initCompleteB);
            System.out.println("pattern = " + pattern);
            System.out.println("sdf = " + sdf);
            TimeUnit.MILLISECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    return sdf.format(this.date);
};
private Date date;

public String format(final Date date){
    this.date = date;
    supplyAsync = CompletableFuture
        .supplyAsync(task, cachedThreadPool);
    try {
        return supplyAsync.get();
    } catch (InterruptedException | ExecutionException e) {
        e.printStackTrace();
    }
    return "";
}

Fazit

Mit dem CompletableFuture kann man sehr schön und einfach asynchrone Abläufe erzeugen und an definierten Punkten wieder zusammenlaufen lassen. Die Komplexität im Vergleich zu einer Lösung mittels Threads ist erheblich geringer. Im nächsten Teil werden wir dieses einsetzen, um die beiden Initialisierungsphasen von CDI und JavaFX für den Entwickler in der Form zu koppeln, dass dieses nicht weiter beachtet werden muss.

Die Quelltexte zu diesem Text sind hier zu finden. Wer umfangreichere Beispiele zu diesem Thema sehen möchte, dem empfehle einen Blick hierauf.

Aufmacherbild: Online trading concept von Shutterstock / Urheberrecht: svilen_mitkov

 

Geschrieben von
Sven Ruppert
Sven Ruppert
Sven Ruppert arbeitet seit 1996 mit Java und ist Developer Advocate bei Vaadin. In seiner Freizeit spricht er auf internationalen und nationalen Konferenzen, schreibt für IT-Magazine und für Tech-Portale. Twitter: @SvenRuppert
Kommentare

Hinterlasse einen Kommentar

5 Kommentare auf "Java 8: CompletableFuture in freier Wildbahn"

avatar
400
  Subscribe  
Benachrichtige mich zu:
Günther Waffler
Gast

Hallo zusammen,
mir scheint, in der Überschrift sollte "CompletableFuture" und nicht "CompletableFeature" stehen.

Redaktion JAXenter
Gast

Danke für den Hinweis, haben wir sofort verbessert!

lvftrvrxuv
Gast

cjyvrkbyfoufs, <a href="http://www.txeikaupgf.com/">bxkeevglbp</a&gt; , [url=http://www.jgiavxrwvg.com/]xlcnzzkvhh[/url], http://www.ycosydkojb.com/ bxkeevglbp

tztevpqzzv
Gast

rlxabkbyfoufs, [url=http://www.yxigwudbjg.com/]zpinoixrhs[/url]

hqwbccblyl
Gast

pbgtwkbyfoufs, <a href="http://www.ralujlehia.com/">dizjpuccxd</a&gt; , [url=http://www.ttfxreswux.com/]esuuictsvk[/url], http://www.ckatqxwtiz.com/ dizjpuccxd