Le multi-threading est une méthode d'écriture de code pour exécuter des tâches en parallèle. Java a eu un excellent support pour l'écriture de code multithread depuis les premiers jours de Java 1.0. Les récentes améliorations apportées à Java ont augmenté les façons dont le code peut être structuré pour incorporer le multi-threading dans les programmes Java.
Dans cet article, nous comparons quelques-unes de ces options afin que vous puissiez mieux juger quelle option utiliser pour votre prochain projet Java.
Java fournit un Thread classe qui peut être étendue pour implémenter le run() méthode. Cette méthode run() est l'endroit où vous implémentez votre tâche. Lorsque vous souhaitez lancer la tâche dans son propre thread, vous pouvez créer une instance de cette classe et invoquer son start() méthode. Cela démarre l'exécution du thread et s'exécute jusqu'à la fin (ou se termine par une exception).
Voici une simple classe Thread qui dort pendant un intervalle spécifié afin de simuler une opération de longue durée.
la classe publique MyThread étend Thread
{
privé int sleepFor ;
public MonThread(int sleepFor) {
this.sleepFor =sleepFor;
}
@Passer outre
public void run() {
System.out.printf("Le thread [%s] démarre
",
Thread.currentThread().toString());
essayez { Thread.sleep(this.sleepFor); }
catch(InterruptedException ex) {}
System.out.printf("[%s] fin du thread
",
Thread.currentThread().toString());
}
}
Créez une instance de cette classe Thread en lui donnant le nombre de millisecondes pour dormir.
travailleur MyThread =new MyThread(sleepFor);
Lancez l'exécution de ce thread de travail en appelant sa méthode start(). Cette méthode rend immédiatement le contrôle à l'appelant, sans attendre que le thread se termine.
worker.start();
System.out.printf("[%s] fil principal
", Thread.currentThread().toString());
Et voici la sortie de l'exécution de ce code. Cela indique que le diagnostic du thread principal est imprimé avant l'exécution du thread de travail.
[Thread[main,5,main]] fil principal
[Thread[Thread-0,5,main]] démarrage du thread
[Thread[Thread-0,5,main]] fil fin
Puisqu'il n'y a plus d'instructions après le démarrage du thread de travail, le thread principal attend que le thread de travail se termine avant de quitter le programme. Cela permet au thread de travail de terminer sa tâche.
Java fournit également une interface appelée Runnable qui peut être implémenté par une classe de travail pour exécuter la tâche dans son run() méthode. Il s'agit d'une autre façon de créer une classe de travail plutôt que d'étendre le Thread classe (décrite ci-dessus).
Voici l'implémentation de la classe worker qui implémente maintenant Runnable au lieu d'étendre Thread.
la classe publique MyThread2 implémente Runnable {
// comme ci-dessus
}
L'avantage d'implémenter l'interface Runnable au lieu d'étendre la classe Thread est que la classe de travail peut désormais étendre une classe spécifique à un domaine dans une hiérarchie de classes.
Qu'est-ce que cela signifie ?
Disons, par exemple, que vous avez un Fruit classe qui implémente certaines caractéristiques génériques des fruits. Maintenant, vous voulez mettre en œuvre un Papaya classe spécialisée dans certaines caractéristiques des fruits. Vous pouvez le faire en ayant la papaye la classe étend le Fruit classe.
classe publique Fruit {
// spécificités des fruits ici
}
la classe publique Papaye étend Fruit {
// remplace le comportement spécifique à la papaye ici
}
Supposons maintenant que vous ayez une tâche fastidieuse que Papaya doit prendre en charge, qui peut être effectuée dans un thread séparé. Ce cas peut être géré en demandant à la classe Papaya d'implémenter Runnable et de fournir la méthode run() où cette tâche est effectuée.
la classe publique Papaya étend Fruit implements Runnable {
// remplace le comportement spécifique à la papaye ici
@Passer outre
public void run() {
// tâche qui prend du temps ici.
}
}
Pour lancer le thread de travail, vous créez une instance de la classe de travail et la remettez à une instance de Thread lors de la création. Lorsque la méthode start() du Thread est invoquée, la tâche s'exécute dans un thread séparé.
Papaye papaye =new Papaye();
// définit les propriétés et invoque les méthodes papaye ici.
Thread thread =new Thread(papaya);
thread.start();
Et c'est un bref résumé de la façon d'utiliser un Runnable pour implémenter une tâche s'exécutant dans un thread.
À partir de la version 1.5, Java fournit un ExecutorService comme un nouveau paradigme pour la création et la gestion des threads au sein d'un programme. Il généralise le concept d'exécution de threads en faisant abstraction de la création de threads.
En effet, vous pouvez exécuter vos tâches dans un pool de threads aussi facilement qu'en utilisant un thread séparé pour chaque tâche. Cela permet à votre programme de suivre et de gérer le nombre de threads utilisés pour les tâches de travail.
Supposons que vous ayez 100 tâches de travail en attente d'exécution. Si vous démarrez un thread par travailleur (comme présenté ci-dessus), vous auriez 100 threads dans votre programme, ce qui pourrait entraîner des goulots d'étranglement ailleurs dans le programme. Au lieu de cela, si vous utilisez un pool de threads avec, disons, 10 threads pré-alloués, vos 100 tâches seront exécutées par ces threads l'une après l'autre afin que votre programme ne manque pas de ressources. De plus, ces threads de pool de threads peuvent être configurés de sorte qu'ils traînent pour effectuer des tâches supplémentaires pour vous.
Un ExecutorService accepte un Runnable tâche (expliquée ci-dessus) et exécute la tâche à un moment approprié. Le soumettre() La méthode, qui accepte la tâche Runnable, renvoie une instance d'une classe appelée Future , qui permet à l'appelant de suivre l'état de la tâche. En particulier, le get() permet à l'appelant d'attendre la fin de la tâche (et fournit le code de retour, le cas échéant).
Dans l'exemple ci-dessous, nous créons un ExecutorService en utilisant la méthode statique newSingleThreadExecutor() , qui, comme son nom l'indique, crée un thread unique pour l'exécution des tâches. Si plusieurs tâches sont soumises pendant qu'une tâche est en cours d'exécution, ExecutorService met ces tâches en file d'attente pour une exécution ultérieure.
L'implémentation Runnable que nous utilisons ici est la même que celle décrite ci-dessus.
ExecutorService esvc =Executors.newSingleThreadExecutor();
Travailleur exécutable =new MyThread2(sleepFor);
Future> future =esvc.submit(worker);
System.out.printf("[%s] fil principal
", Thread.currentThread().toString());
future.get();
esvc.shutdown();
Notez qu'un ExecutorService doit être correctement arrêté lorsqu'il n'est plus nécessaire pour d'autres soumissions de tâches.
À partir de la version 1.5, Java a introduit une nouvelle interface appelée Callable . Elle est similaire à l'ancienne interface Runnable à la différence que la méthode d'exécution (appelée call() au lieu de run() ) peut renvoyer une valeur. De plus, il peut également déclarer qu'une Exception peut être lancé.
Un ExecutorService peut également accepter des tâches implémentées comme Callable et renvoie un Future avec la valeur renvoyée par la méthode à la fin.
Voici un exemple Mangue classe qui étend la classe Fruit classe définie précédemment et implémente le Callable interface. Une tâche coûteuse et chronophage est effectuée dans le call() méthode.
{Et voici le code pour soumettre une instance de la classe à un ExecutorService. Le code ci-dessous attend également que la tâche se termine et imprime sa valeur de retour.
ExecutorService esvc =Executors.newSingleThreadExecutor();
Travailleur MyCallable =new MyCallable(sleepFor);
Futur futur =esvc.submit(worker);
System.out.printf("[%s] fil principal
", Thread.currentThread().toString());
System.out.println("Tâche retournée :" + future.get());
esvc.shutdown();
Dans cet article, nous avons appris quelques méthodes pour écrire du code multithread en Java. Ceux-ci incluent :
Laquelle de ces options pensez-vous utiliser dans votre prochain projet ? Faites-le nous savoir dans les commentaires ci-dessous.