[Java] Ich möchte eine asynchrone Verarbeitung mit Promise in der Java-Testversion der Promise-ähnlichen Syntax von JavaScript schreiben.

Überblick

――In der Java-Sprache dachte ich: "Ich möchte asynchrone Verarbeitung und parallele Verarbeitung mit ** JavaScript Promise-like ** </ font> Grammatik schreiben."

  • Die Quelle befindet sich im folgenden Repository https://github.com/riversun/java-promise

Codebeispiel

Wie soll ich den Prozess "Wenn ** asynchroner Prozess 1 ** beendet ist, ** asynchroner Prozess 2 ** mit dem Ergebnis ausgeführt wird" in Java schreiben?

  • Lösung 1: Lösung in der Java 1.4-Ära: Geben Sie Ihr Bestes mit der Funktion der Thread-Klasse
  • Threads verschachtelt halten oder mit Join auf das Ende warten. Der Beginn der Parallelverarbeitung.
  • Lösung 2: Lösung in der Java5 (1.5) -Ära: Ich frage mich, ob ich mit Callable / Future zufrieden war ... ――Ich bin froh, die Ergebnisse mit Future / Callable zurückgeben zu können, und ich habe alle Requisiten wie Semaphos und Latches, aber ich muss mein Bestes geben.
  • Lösung 3: Lösung in der Java 8-Ära: Sie sollten mit Completable Future zufrieden sein.
  • Endlich ist der lang erwartete Future / Promise-Mustermechanismus jetzt als Standard verfügbar!

――In diesem Artikel habe ich den folgenden Code in einer JavaScript Promise-ähnlichen Weise aus einer anderen Perspektive als die oben genannten drei Lösungen geschrieben.

Versprechen Sie Beispiel in Java


Promise.resolve()
 .then((action, data) -> {
   //Asynchrone Verarbeitung 1
   new Thread(() -> {System.out.println("Process1");action.resolve("Result-1");}).start();
 })
 .then((action, data) -> {
   //Asynchrone Verarbeitung 2
   new Thread(() -> {System.out.println("Process2");action.resolve("Result-2");}).start();
 })
 .start();

Was ich in diesem Artikel machen möchte

――Was Sie tun möchten, ist ** "In JavaScript Promise-like schreiben" ** </ font>

  • Eine Reihe von Verarbeitungen, die mehrere APIs asynchron aufrufen und die nächste API aufrufen, wenn das Ergebnis empfangen wird. --Prozess, der mehrere Prozesse gleichzeitig (parallel) verschiebt und zum nächsten Prozess übergeht, wenn alle abgeschlossen sind.

In diesem Artikel nicht behandelt

-Realisierung des (akademischen) Zukunfts- / Versprechensmusters

  • Verwendung der gleichzeitigen Java-Standardverarbeitung
  • ExecutorService und Callable //docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/Callable.html)
  • So schreiben Sie mit CompletableFuture, das von Java 8 oder höher verwendet werden kann

Zielumgebung

  • Java 5 oder höher
  • Die Bibliothek funktioniert auch mit Java 1.6-basiertem Android
  • Die gleichzeitige Java 8-API wird nicht verwendet

Verwendung (Abhängigkeit)

Es befindet sich im Maven-Repository als Bibliothek ** Java-Versprechen **, sodass Sie es sofort verwenden können, indem Sie Folgendes hinzufügen.

Maven

POM.XML-Abhängigkeit


<dependency>
	<groupId>org.riversun</groupId>
	<artifactId>java-promise</artifactId>
	<version>1.1.0</version>
</dependency>

Gradle

build.gradle


dependencies {
    compile 'org.riversun:java-promise:1.1.0'
}

build.gradle(Android)


dependencies {
    implementation 'org.riversun:java-promise:1.1.0'
}

Hauptgeschichte

Vergleich zwischen in JavaScript geschriebenem Versprechen und der in diesem Artikel vorgestellten in Java geschriebenen Methode

** Schreiben Sie zuerst ein Versprechen in JavaScript zum Vergleich **

Der folgende Code ist ein JavaScript Beispiel, das das asynchron ausgeführte Verarbeitungsergebnis ('bar') einfach mit dem String 'foo' verknüpft. Web / JavaScript / Referenz / Global_Objects / Promise / then # Chaining) Code. Dies ist ein Auszug aus dem als Beispiel für Promise in [MDN] veröffentlichten (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/then#Chaining).

Example.js


Promise.resolve('foo')
    .then(function (data) {
        return new Promise(function (resolve, reject) {
            setTimeout(function () {
                const newData = data + 'bar';
                resolve(newData);
            }, 1);
        });
    })
    .then(function (data) {
        return new Promise(function (resolve, reject) {
            console.log(data);
            resolve();
        });
    });
console.log("Promise in JavaScript");

Das Ausführungsergebnis ist wie folgt

Promise in JavaScript
foobar

** Als nächstes schreiben Sie mit Java-Versprechen in Java 8 **

Example.java


import org.riversun.promise.Promise;

public class Example {

    public static void main(String[] args) {

        Promise.resolve("foo")
                .then(new Promise((action, data) -> {
                    new Thread(() -> {
                        String newData = data + "bar";
                        action.resolve(newData);//#Fahren Sie mit Entschlossenheit zum nächsten Prozess fort
                    }).start();//In einem separaten Thread ausführen
                }))
                .then(new Promise((action, data) -> {
                    System.out.println(data);
                    action.resolve();
                }))
                .start();//Auslöser zum Starten der Verarbeitung
        System.out.println("Promise in Java");
    }
}

Das Ausführungsergebnis ist wie folgt

Promise in Java
foobar

Da die Ausführung unter Promise asynchron ausgeführt wird (separater Thread), können Sie sehen, dass in diesem Beispiel System.out.println (" Promise in Java "); ausgeführt wird.

Zur Vereinfachung der Verarbeitung habe ich versucht, der Promise-ähnlichen Syntax von JavaScript näher zu kommen, außer dass ich am Ende ".start ()" aufgerufen habe, um die Promise-Kette auszulösen.

Notation

Schreiben Sie ohne Lambda-Ausdrücke (Java 7 oder früher)

Wenn Sie den Lambda-Ausdruck nicht verwenden, lautet er wie folgt

Wenn ohne Verwendung des Lambda-Ausdrucks geschrieben


Promise.resolve("foo")
        .then(new Promise(new Func() {
            @Override
            public void run(Action action, Object data) throws Exception {
                new Thread(() -> {
                    String newData = data + "bar";
                    action.resolve(newData);
                }).start();
            }
        }))
        .then(new Promise(new Func() {
            @Override
            public void run(Action action, Object data) throws Exception {
                new Thread(() -> {
                    System.out.println(data);
                    action.resolve();
                }).start();
            }
        }))
        .start();

Die Identität des Teils, in dem (Aktion, Daten) -> {} die Schnittstelle ist, die die Funktion in JavaScript darstellt.

Func.java


public interface Func {
    public void run(Action action, Object data) throws Exception;
}

Einfacher schreiben

Sie können "Promise.then (new Func ())" anstelle von "Promise.then (new Promise ())" verwenden. Wenn Sie "new Func" durch einen Lambda-Ausdruck ersetzen, wird es zu "Promise.then ((Aktion, Daten) -> {})", was es noch einfacher macht.

then(Func)Schreiben Sie mit


Promise.resolve("foo")
   .then((action, data) -> {
       new Thread(() -> {
           String newData = data + "bar";
           action.resolve(newData);
       }).start();
   })
   .then((action, data) -> {
       System.out.println(data);
       action.resolve();
   })
   .start();

Einführung verschiedener Muster der parallelen Ausführung mit Promise

(1) Promise.then: </ font> Führen Sie die asynchrone Verarbeitung der Reihe nach aus

Code:

public class Example20 {

    public static void main(String[] args) {

        //Prozess 1 (Ausführung eines anderen Threads)
        Func function1 = (action, data) -> {
            new Thread(() -> {
                System.out.println("Process-1");
                Promise.sleep(1000);// Thread.Gleich wie Schlaf
                action.resolve("Result-1");//Status"fulfilled"Und das Ergebnis im nächsten Prozess("Result-1")Sagen
            }).start();//Starten Sie die asynchrone Verarbeitung in einem anderen Thread
        };

        //Prozess 2
        Func function2 = (action, data) -> {
            System.out.println("Process-2 result=" + data);
            action.resolve();
        };

        Promise.resolve()//Starten Sie die Verarbeitung
                .then(function1)//Ausführung von Prozess 1
                .then(function2)//Ausführung von Prozess 2
                .start();//Start

        System.out.println("Hello,Promise");
    }

** Ausführungsergebnis: **

Hello,Promise
Process-1
Process-2 result=Result-1

Erläuterung: Die Grammatik von ** dann ** lautet ** Promise.then (onFulfilled [, onRejected]); ** Das heißt, es können bis zu zwei Argumente verwendet werden. Das erste Argument ** onFulfilled ** wird ausgeführt, wenn die vorherige Ausführung mit dem Status erfüllt (≈successful) </ font> beendet wurde. Das zweite Argument ** onRejected ** ist optional, wird jedoch ausgeführt, wenn die vorherige Ausführung mit dem Status abgelehnt (≒ fehlgeschlagen) </ font> beendet wurde. Dieses Beispiel gibt nur das erste Argument an.

** Verarbeitungsablauf: **

image.png

  1. Setzen Sie den Status auf fullfilled </ font> mit ** Promise.resolve ** und verketten Sie auf ** dann **.
  2. Da </ font> erfüllt ist, führt ** dann ** die im ersten Argument angegebene ** Funktion1 ** aus.
  3. ** function1 ** ändert auch den Status in fullfilled </ font> von ** action.resolve **
  4. ** function1 ** setzt ** action.resolve ** auf das Argument vom Typ String ** "Result-1" **
  5. Das nächste ** dann ** hat ebenfalls den Status fullfilled </ font>, sodass ** function2 ** ausgeführt wird.
  6. ** Funktion2 ** Ausführungsargument ** Daten ** enthält das Ergebnis ** "Ergebnis-1" ** von ** Funktion1 **

(2) action.resolve, action.reject: </ font> Verzweigen Sie den Prozess abhängig vom Ausführungsergebnis

Code:

public class Example21 {

    public static void main(String[] args) {

        Func function1 = (action, data) -> {
            System.out.println("Process-1");
            action.reject();//Status"rejected"Auf Ausführung setzen und abschließen
        };

        Func function2_1 = (action, data) -> {
            System.out.println("Resolved Process-2");
            action.resolve();
        };

        Func function2_2 = (action, data) -> {
            System.out.println("Rejected Process-2");
            action.resolve();
        };

        Promise.resolve()
                .then(function1)
                .then(
                        function2_1, //Wird ausgeführt, wenn der Status erfüllt ist
                        function2_2 //Wird ausgeführt, wenn der Status abgelehnt wird
                )
                .start();

        System.out.println("Hello,Promise");

    }
}

** Ausführungsergebnis: **

Hello,Promise
Process-1
Rejected Process-2

Erläuterung:

** Funktion1 ** ist

action.reject();

Da dies abgeschlossen ist, wird der Status abgelehnt </ font>. Das nächste ** dann ** ist

 .then(
         function2_1, //Wird ausgeführt, wenn der Status erfüllt ist
         function2_2 //Wird ausgeführt, wenn der Status abgelehnt wird
 )

Es wird gesagt. Wie oben erwähnt, lautet die Syntax von ** dann ** ** Promise.then (onFulfilled [, onRejected]); **, also Da der Abschlussstatus von ** function1 ** abgelehnt wird </ font>, wird hier ** function2_2 ** ausgeführt, das zweite Argument von ** then **.

** Verarbeitungsablauf: **

image.png

(3) ** Promise.always: ** </ font> Erhält sowohl Auflösungs- als auch Ablehnungsergebnisse

Code:

public class Example30 {

    public static void main(String[] args) {
        Func function1 = (action, data) -> {
            action.reject("I send REJECT");
        };
        Func function2 = (action, data) -> {
            System.out.println("Received:" + data);
            action.resolve();
        };
        Promise.resolve()
                .then(function1)
                .always(function2)//Der Status ist"fulfilled"Aber"rejected"Aber実行される
                .start();
    }
}

** Ausführungsergebnis: **

Received:I send REJECT

Erläuterung:

.always(function2)

** immer ((Aktion, Daten) -> {}) ** hat den Status <Schriftfarbe aufgrund abgelehnt, auch wenn der vorherige Prozess den Status erfüllt </ font> aufgrund aufgelöst hatte. Wird immer ausgeführt, unabhängig von = rot> abgelehnt </ font>.

** Verarbeitungsablauf: **

image.png

(4) ** Promise.all **: </ font> Warten Sie, bis mehrere parallele asynchrone Prozesse abgeschlossen sind, und fahren Sie mit dem nächsten fort

Code:

public class Example40 {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        //Asynchrone Verarbeitung 1
        Func function1 = (action, data) -> {
            new Thread(() -> {
                Promise.sleep(1000); System.out.println("func1 running");action.resolve("func1-result");
            }).start();
        };
        //Asynchrone Verarbeitung 2
        Func function2 = (action, data) -> {
            new Thread(() -> {
            Promise.sleep(500);System.out.println("func2 running"); action.resolve("func2-result");
            }).start();
        };
        //Asynchrone Verarbeitung 3
        Func function3 = (action, data) -> {
            new Thread(() -> {
            Promise.sleep(100);System.out.println("func3 running");action.resolve("func3-result");
            }).start();
        };
        //Der Prozess des endgültigen Empfangs des Ergebnisses
        Func function4 = (action, data) -> {
            System.out.println("Erhielt das Ergebnis");
            List<Object> resultList = (List<Object>) data;
            for (int i = 0; i < resultList.size(); i++) {
                Object result = resultList.get(i);
                System.out.println("Asynchrone Verarbeitung" + (i + 1) + "Das Ergebnis von" + result);
            }
            action.resolve();
        };
        
        Promise.all(function1, function2, function3)
                .always(function4)
                .start();
    }
}

** Ausführungsergebnis: **

func3 running
func2 running
func1 running
Erhielt das Ergebnis der asynchronen Verarbeitung
Das Ergebnis des asynchronen Prozesses 1 ist func1-result
Das Ergebnis der asynchronen Verarbeitung 2 ist func2-result
Das Ergebnis der asynchronen Verarbeitung 3 ist func3-result

Erläuterung:

  • ** Promise.all (function1, function2, ... functionN) ** kann mehrere Prozesse von ** function1 ** bis ** functionN ** als Argumente verwenden und parallel ausführen.

--Wenn die parallele Ausführung beendet ist, wechselt der Prozess zum verketteten ** dann ** (hier ** immer **).

  • Im obigen Beispiel werden ** Funktion1, Funktion2, Funktion3 ** parallel ausgeführt, aber wenn ** Funktion1 bis Funktion3 ** alle mit erfüllt </ font> abgeschlossen sind, wird jeder * Die Ergebnisse von * Funktion1 bis Funktion3 ** werden in ** Liste ** gespeichert und an ** dann ** übergeben. Zu diesem Zeitpunkt ist die Speicherreihenfolge die im Argument angegebene Reihenfolge von ** Funktion1, Funktion2, Funktion3 **. (Diese Spezifikation entspricht auch dem JavaScript-Versprechen.)

  • ** Wenn eine der Funktionen1 bis Funktion3 ** fehlschlägt ≒ ablehnen </ font>, wird zuerst </ font> abgelehnt. Das Ergebnis (Ablehnungsgrund) der Funktion wird an das nächste ** dann ** übergeben. (Fail-Fast-Prinzip)

** Verarbeitungsablauf: **

image.png

(5) ** Promise.all **: Teil 2 </ font> Geben Sie den Thread-Pool selbst an

Wie in (4) erläutert, kann ** Func ** parallel zu ** Promise.all ** betrieben werden, aber ** Executor ** wird als Thread-Erstellungsrichtlinie verwendet, wenn der Parallelbetrieb im Voraus ausgeführt wird. Kann definiert werden. Außerdem kann der Thread-Pool, der bereits für einen anderen Zweck vorbereitet wurde, auf ** Promise.all ** umgeleitet werden.

** Codebeispiel: **

public class Example41 {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {

        final ExecutorService myExecutor = Executors.newFixedThreadPool(2);

        //Asynchrone Verarbeitung 1
        Func function1 = (action, data) -> {
            System.out.println("No.1 " + Thread.currentThread());
            new Thread(() -> {
                Promise.sleep(1000);System.out.println("func1 running");action.resolve("func1-result");
            }).start();
        };

        //Asynchrone Verarbeitung 2
        Func function2 = (action, data) -> {
            System.out.println("No.2 " + Thread.currentThread());
            new Thread(() -> {
                Promise.sleep(500);System.out.println("func2 running");action.resolve("func2-result");
            }).start();
        };

        //Asynchrone Verarbeitung 3
        Func function3 = (action, data) -> {
            System.out.println("No.3 " + Thread.currentThread());
            new Thread(() -> {
                Promise.sleep(100);System.out.println("func3 running");action.resolve("func3-result");
            }).start();
        };
        
        //Der Prozess des endgültigen Empfangs des Ergebnisses
        Func function4 = (action, data) -> {
            System.out.println("No.4 final " + Thread.currentThread());
            System.out.println("Erhielt das Ergebnis");
            List<Object> resultList = (List<Object>) data;
            for (int i = 0; i < resultList.size(); i++) {
                Object result = resultList.get(i);
                System.out.println("Asynchrone Verarbeitung" + (i + 1) + "Das Ergebnis von" + result);
            }
            myExecutor.shutdown();
            action.resolve();
        };

        Promise.all(myExecutor, function1, function2, function3)
                .always(function4)
                .start();
    }
}

** Ausführungsergebnis: **

No.1 Thread[pool-1-thread-2,5,main]
No.2 Thread[pool-1-thread-2,5,main]
No.3 Thread[pool-1-thread-2,5,main]
func3 running
func2 running
func1 running
No.4 final Thread[pool-1-thread-1,5,main]
Erhielt das Ergebnis
Das Ergebnis des asynchronen Prozesses 1 ist func1-result
Das Ergebnis der asynchronen Verarbeitung 2 ist func2-result
Das Ergebnis der asynchronen Verarbeitung 3 ist func3-result

Die Ergebnisse zeigen, dass ** Func ** auf Threads ausgeführt wird, die aus demselben Thread-Pool stammen. (Da die asynchrone Verarbeitung (neuer Thread) absichtlich in Func ausgeführt wird, befindet sich die asynchrone Verarbeitung außerhalb des angegebenen Thread-Pools.)

Erläuterung:

-Definieren Sie den ** Executor **, der zum Ausführen von ** Promise.all ** verwendet wird. Unten ist ein Thread-Pool mit einer Poolgröße von 2.

final ExecutorService myExecutor = Executors.newFixedThreadPool(2);
  • ** Executor ** kann wie Promise.all angegeben werden (Executor, func1, func2, func3, ... funcN) **
 Promise.all(myExecutor, function1, function2, function3)
         .always(function4)
         .start();
  • Wenn Sie ** Executor ** selbst angeben, vergessen Sie nicht, ** herunterzufahren **
Func function4 = (action, data) -> {
   //Unterlassung
    myExecutor.shutdown();
    action.resolve();
};

** Threading-Richtlinie: **

  • Die Größe des Thread-Pools muss ** 2 ** oder größer sein. </ font> (Das heißt, singleThreadExecutor ist nicht verfügbar.) -In ** Java-Versprechen ** wird bei Ausführung von ** Promise.all ** ein Thread für die asynchrone Ausführung verwendet.
  • Da ** Promise.all ** für die parallele Ausführung verwendet wird, ist außerdem mindestens ein Thread für die parallele Ausführung erforderlich. (Obwohl ein Thread nicht als parallel bezeichnet wird)
  • Wenn Sie diese beiden hinzufügen, benötigen Sie zwei oder mehr Threads.

Zusammenfassung

  • Ich habe versucht, "Versprechen wie JavaScript" in Java zu schreiben

  • Wenn Sie den Java8-Lambda-Ausdruck gut verwenden, können Sie Promise in einer Form ausführen, die der JavaScript-Notation nahe kommt. ――Ich möchte aus der Entwicklung von JavaScript (ES) und anderen skriptbasierten Sprachen lernen, dass Sie mit einfacher Notation eine raffinierte Verarbeitung durchführen können. (Die asynchrone Ausführung hat sich auch in JavaScript zu ** async / await ** entwickelt.)

  • Der bibliothekseitige Quellcode von Promise (** Java-Versprechen **) in Java ist unten https://github.com/riversun/java-promise --git Klon https: // github.com / Riversun / Java-Versprechen.git --mvn test gibt Ihnen einen Unit-Test.

  • Darüber hinaus ist der in diesem Artikel veröffentlichte Beispielcode unten aufgeführt. https://github.com/riversun/java-promise-examples/tree/master-ja

Recommended Posts