[Java] Je souhaite écrire un traitement asynchrone à l'aide de Promise dans Java-Trial of Promise-like syntax of JavaScript-

Aperçu

«En langage Java, j'ai pensé:« Je veux écrire un traitement asynchrone et un traitement parallèle avec une grammaire ** JavaScript Promise-like ** </ font> ». --La source est dans le référentiel suivant https://github.com/riversun/java-promise

Exemple de code

Comment écrire le processus «Lorsque ** le processus asynchrone 1 ** est terminé, ** le processus asynchrone 2 ** est exécuté en utilisant le résultat» en Java?

--Solution 1: Solution à l'ère de Java 1.4: faites de votre mieux avec la fonction de classe Thread --Maintenir les threads de manière imbriquée ou attendre la fin avec jointure. L'aube du traitement parallèle. --Solution 2: Solution à l'ère Java5 (1.5): je me demande si j'étais satisfait de Callable / Future ... Je suis heureux que vous puissiez renvoyer le résultat avec Future / Callable, et en plus, j'ai tous les accessoires tels que semapho et latch, mais je dois faire de mon mieux. --Solution 3: Solution à l'ère de Java 8: Vous devriez être satisfait de Completable Future. --Enfin, le mécanisme de pattern Future / Promise tant attendu est maintenant disponible en standard!

Dans cet article, j'ai écrit le code suivant à la manière d'une promesse JavaScript dans une perspective différente de celle des trois solutions ci-dessus.

Exemple de promesse en Java


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

Ce que je veux faire dans cet article

  • Ce que vous voulez faire, c'est ** "Écrire en JavaScript comme une promesse" ** </ font>
  • Une série de traitements qui appelle plusieurs API de manière asynchrone et appelle l'API suivante lorsque le résultat est reçu.
  • Processus qui déplace plusieurs processus simultanément (en parallèle) et passe au processus suivant lorsque tous sont terminés.

Non couvert dans cet article

-Réalisation du modèle Future / Promise (académique)

  • Comment utiliser le traitement simultané standard Java
  • ExecutorService et [Callable](https://docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/ExecutorService5 ou version ultérieure) //docs.oracle.com/javase/1.5.0/docs/api/java/util/concurrent/Callable.html)
  • Comment écrire à l'aide de CompletableFuture qui peut être utilisé à partir de Java 8 ou version ultérieure

Environnement cible

--Java 5 ou version ultérieure

  • La bibliothèque fonctionne également sur Android basé sur Java 1.6
  • L'API simultanée Java 8 n'est pas utilisée

Comment utiliser (dépendance)

Il se trouve dans le référentiel Maven en tant que bibliothèque ** java-promise **, vous pouvez donc l'utiliser immédiatement en ajoutant ce qui suit.

Maven

POM.dépendance xml


<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'
}

Histoire principale

Comparaison entre Promise écrite en JavaScript et la méthode écrite en Java présentée dans cet article

** Tout d'abord, rédigez une promesse en JavaScript pour comparaison **

Le code suivant est un [exemple] JavaScript (https://developer.mozilla.org/en-US/docs/) qui concatène simplement le résultat du traitement exécuté de manière asynchrone («bar») à la chaîne «foo». Code Web / JavaScript / Référence / Global_Objects / Promise / puis # Chaining). Extrait de ce qui est publié comme exemple de Promise dans MDN.

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");

Le résultat de l'exécution est le suivant

Promise in JavaScript
foobar

** Ensuite, écrivez en utilisant java-promise dans 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);//#Passer au processus suivant avec résolution
                    }).start();//Exécuter dans un thread séparé
                }))
                .then(new Promise((action, data) -> {
                    System.out.println(data);
                    action.resolve();
                }))
                .start();//Déclencheur pour démarrer le traitement
        System.out.println("Promise in Java");
    }
}

Le résultat de l'exécution est le suivant

Promise in Java
foobar

Puisque l'exécution ci-dessous Promise est effectuée de manière asynchrone (thread séparé), vous pouvez voir que System.out.println (" Promise in Java "); est exécuté dans cet exemple.

Pour faciliter le traitement, j'ai essayé de me rapprocher de la syntaxe de JavaScript de type Promise, sauf que j'ai appelé .start () à la fin pour déclencher la chaîne Promise.

notation

Écrire sans expressions lambda (Java 7 ou version antérieure)

Si vous n'utilisez pas l'expression lambda, ce sera comme suit

Lorsqu'il est écrit sans utiliser l'expression lambda


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

L'identité de la partie où (action, données) -> {} est l'interface qui représente la fonction en JavaScript.

Func.java


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

Écrivez plus simplement

Vous pouvez utiliser Promise.then (new Func ()) au lieu de Promise.then (new Promise ()). Si vous remplacez new Func par une expression lambda, cela devient Promise.then ((action, data) -> {}), ce qui le rend encore plus simple.

then(Func)Écrire en utilisant


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

Introduction de divers modèles d'exécution parallèle à l'aide de Promise

(1) Promise.then: </ font> Exécuter le traitement asynchrone dans l'ordre

code:

public class Example20 {

    public static void main(String[] args) {

        //Processus 1 (exécution d'un autre thread)
        Func function1 = (action, data) -> {
            new Thread(() -> {
                System.out.println("Process-1");
                Promise.sleep(1000);// Thread.Identique au sommeil
                action.resolve("Result-1");//Statut"fulfilled"Et le résultat dans le processus suivant("Result-1")Dire
            }).start();//Démarrer le traitement asynchrone dans un autre thread
        };

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

        Promise.resolve()//Commencer le traitement
                .then(function1)//Exécution du processus 1
                .then(function2)//Exécution du processus 2
                .start();//début

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

** Résultat d'exécution: **

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

La description: La grammaire de ** then ** est ** Promise.then (onFulfilled [, onRejected]); ** Autrement dit, cela peut prendre jusqu'à deux arguments. Le premier argument ** onFulfilled ** est exécuté si l'exécution précédente s'est terminée avec le statut rempli (≒ réussi) </ font>. Le deuxième argument ** onRejected ** est facultatif, mais il sera exécuté si l'exécution précédente s'est terminée avec le statut rejeté (≒ échoué) </ font>. Cet exemple spécifie uniquement le premier argument.

** Flux de traitement: **

image.png

  1. Définissez le statut sur fullfilled </ font> avec ** Promise.resolve ** et enchaînez sur ** puis **.
  2. Puisque fullfilled </ font>, ** then ** exécute ** function1 ** spécifié dans le premier argument.
  3. ** function1 ** change également le statut en fullfilled </ font> par ** action.resolve **
  4. ** function1 ** définit ** action.resolve ** sur l'argument de type String ** "Result-1" **
  5. Le prochain ** then ** a également un statut de fullfilled </ font>, donc ** function2 ** est exécutée.
  6. ** function2 ** Argument d'exécution ** data ** contient le résultat ** "Result-1" ** de ** function1 **

(2) action.resolve, action.reject: </ font> Branche le processus en fonction du résultat de l'exécution

code:

public class Example21 {

    public static void main(String[] args) {

        Func function1 = (action, data) -> {
            System.out.println("Process-1");
            action.reject();//Statut"rejected"Définir et terminer l'exécution
        };

        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, //Exécuté lorsque le statut est rempli
                        function2_2 //Exécuté lorsque le statut est rejeté
                )
                .start();

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

    }
}

** Résultat d'exécution: **

Hello,Promise
Process-1
Rejected Process-2

La description:

** function1 ** est

action.reject();

Puisqu'il est complété par, l'état sera rejeté </ font>. Le prochain ** alors ** est

 .then(
         function2_1, //Exécuté lorsque le statut est rempli
         function2_2 //Exécuté lorsque le statut est rejeté
 )

C'est dit. Comme mentionné ci-dessus, la syntaxe de ** then ** est ** Promise.then (onFulfilled [, onRejected]); **, donc Puisque l'état d'achèvement de ** fonction1 ** est rejeté </ font>, ** function2_2 **, qui est le deuxième argument de ** puis **, est exécuté ici.

** Flux de traitement: **

image.png

(3) ** Promise.always: ** </ font> Reçoit les résultats du traitement de résolution et de rejet

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)//Le statut est"fulfilled"Mais"rejected"Mais実行される
                .start();
    }
}

** Résultat d'exécution: **

Received:I send REJECT

La description:

.always(function2)

** always ((action, data) -> {}) ** a un statut <couleur de la police en raison de rejet, même si le processus précédent avait un statut accompli </ font> en raison de la résolution. Toujours exécuté indépendamment de = rouge> rejeté </ font>.

** Flux de traitement: **

image.png

(4) ** Promise.all **: </ font> Attendez la fin de plusieurs processus asynchrones parallèles et passez au suivant

code:

public class Example40 {
    @SuppressWarnings("unchecked")
    public static void main(String[] args) {
        //Traitement asynchrone 1
        Func function1 = (action, data) -> {
            new Thread(() -> {
                Promise.sleep(1000); System.out.println("func1 running");action.resolve("func1-result");
            }).start();
        };
        //Traitement asynchrone 2
        Func function2 = (action, data) -> {
            new Thread(() -> {
            Promise.sleep(500);System.out.println("func2 running"); action.resolve("func2-result");
            }).start();
        };
        //Traitement asynchrone 3
        Func function3 = (action, data) -> {
            new Thread(() -> {
            Promise.sleep(100);System.out.println("func3 running");action.resolve("func3-result");
            }).start();
        };
        //Le processus pour finalement recevoir le résultat
        Func function4 = (action, data) -> {
            System.out.println("Reçu le résultat");
            List<Object> resultList = (List<Object>) data;
            for (int i = 0; i < resultList.size(); i++) {
                Object result = resultList.get(i);
                System.out.println("Traitement asynchrone" + (i + 1) + "Le résultat de" + result);
            }
            action.resolve();
        };
        
        Promise.all(function1, function2, function3)
                .always(function4)
                .start();
    }
}

** Résultat d'exécution: **

func3 running
func2 running
func1 running
Reçu le résultat du traitement asynchrone
Le résultat du processus asynchrone 1 est func1-result
Le résultat du traitement asynchrone 2 est func2-result
Le résultat du traitement asynchrone 3 est func3-result

La description:

  • ** Promise.all (fonction1, fonction2, ... fonctionN) ** peut prendre plusieurs processus de ** fonction1 ** à ** fonctionN ** comme arguments et les exécuter en parallèle.

--Lorsque l'exécution parallèle est terminée, le processus passe à la chaîne ** puis ** (ici ** toujours **). --Dans l'exemple ci-dessus, ** function1, function2, function3 ** sont exécutés en parallèle, mais si ** function1 à function3 ** sont tous complétés par rempli </ font>, chacun * Les résultats de * function1 à function3 ** sont stockés dans ** List ** et transmis à ** then **. A ce moment-là, l'ordre de stockage est l'ordre de ** fonction1, fonction2, fonction3 ** spécifié dans l'argument. (Cette spécification est également la même que la promesse JavaScript)

  • ** Si l'une des fonctions 1 à fonction 3 ** échoue ≒ rejette </ font>, ce sera d'abord rejet </ font>. Le résultat (motif de rejet) de la fonction est passé au suivant ** puis **. (Principe Fail-Fast)

** Flux de traitement: **

image.png

(5) ** Promise.all **: Part 2 </ font> Spécifiez vous-même le pool de threads

Comme expliqué dans (4), ** Func ** peut être utilisé en parallèle avec ** Promise.all **, mais ** Executor ** est utilisé comme politique de création de thread lors de l'exécution d'une opération parallèle à l'avance. Peut être défini. De plus, le pool de threads déjà préparé à une autre fin peut être détourné vers ** Promise.all **.

** Exemple de code: **

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

        final ExecutorService myExecutor = Executors.newFixedThreadPool(2);

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

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

        //Traitement asynchrone 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();
        };
        
        //Le processus pour finalement recevoir le résultat
        Func function4 = (action, data) -> {
            System.out.println("No.4 final " + Thread.currentThread());
            System.out.println("Reçu le résultat");
            List<Object> resultList = (List<Object>) data;
            for (int i = 0; i < resultList.size(); i++) {
                Object result = resultList.get(i);
                System.out.println("Traitement asynchrone" + (i + 1) + "Le résultat de" + result);
            }
            myExecutor.shutdown();
            action.resolve();
        };

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

** Résultat d'exécution: **

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]
Reçu le résultat
Le résultat du processus asynchrone 1 est func1-result
Le résultat du traitement asynchrone 2 est func2-result
Le résultat du traitement asynchrone 3 est func3-result

Les résultats montrent que ** Func ** s'exécute sur des threads provenant du même pool de threads. (Étant donné que le traitement asynchrone (nouveau Thread) est intentionnellement effectué dans Func, le traitement asynchrone est en dehors du pool de threads spécifié.)

La description:

-Définissez le ** Executor ** utilisé pour exécuter ** Promise.all **. Vous trouverez ci-dessous un pool de threads avec une taille de pool de 2.

final ExecutorService myExecutor = Executors.newFixedThreadPool(2);
  • ** Executor ** peut être spécifié comme Promise.all (executor, func1, func2, func3, ... funcN) **
 Promise.all(myExecutor, function1, function2, function3)
         .always(function4)
         .start();

--Si vous spécifiez ** Executor ** vous-même, n'oubliez pas de ** arrêter **

Func function4 = (action, data) -> {
   //Omission
    myExecutor.shutdown();
    action.resolve();
};

** Politique de filetage: **

  • La taille du pool de threads doit être de ** 2 ** ou plus. </ font> (Autrement dit, singleThreadExecutor n'est pas disponible.) -Dans ** java-promise **, lorsque ** Promise.all ** est exécuté, un thread est utilisé pour l'exécution asynchrone.
  • De plus, puisque ** Promise.all ** est utilisé pour l'exécution parallèle, au moins un thread est requis pour l'exécution parallèle. (Bien qu'un thread ne soit pas appelé parallèle)
  • Si vous ajoutez ces deux, vous avez besoin de deux threads ou plus.

Résumé

  • J'ai essayé "d'écrire Promise comme JavaScript" en Java
  • Si vous incorporez bien l'expression lambda Java8, vous pouvez exécuter Promise sous une forme proche de la notation JavaScript. ――Je voudrais apprendre de l'évolution de JavaScript (ES) et d'autres langages basés sur des scripts que vous pouvez effectuer un traitement astucieux avec une notation simple. (L'exécution asynchrone a également évolué vers ** async / await ** en JavaScript)
  • Le code source côté bibliothèque de Promise (** java-promise **) en Java est ci-dessous https://github.com/riversun/java-promise --git clone https: // github.com / Riversun / java-promise.git --mvn test vous donnera un test unitaire.

--En outre, l'exemple de code publié dans cet article est ci-dessous. https://github.com/riversun/java-promise-examples/tree/master-ja

Recommended Posts