Introduction aux threads Java

Cet article, l'un des premiers jamais publiés par JavaWorld, décrit comment les threads sont implémentés dans le langage de programmation Java, en commençant par un aperçu général des threads.

En termes simples, un thread est le chemin d'exécution d'un programme. La plupart des programmes écrits aujourd'hui fonctionnent comme un seul thread, ce qui pose des problèmes lorsque plusieurs événements ou actions doivent se produire en même temps. Disons, par exemple, qu'un programme n'est pas capable de dessiner des images tout en lisant des frappes. Le programme doit accorder toute son attention à la saisie au clavier qui n'a pas la capacité de gérer plus d'un événement à la fois. La solution idéale à ce problème est l'exécution transparente de deux ou plusieurs sections d'un programme en même temps. Threads nous permet de faire cela.

En savoir plus sur les threads Java

Cet article fait partie de l'archive de contenu technique JavaWorld. Consultez ce qui suit pour en savoir plus sur les threads Java et la concurrence:

Comprendre les threads Java ( série Java 101 , 2002):

  • Partie 1: Présentation des threads et des exécutables
  • Partie 2: Synchronisation des threads
  • Partie 3: Planification des threads et attente / notification
  • Partie 4: Groupes de threads et volatilité

Articles Liés

  • Java hyper-threadé: utilisation de l'API Java Concurrency (2006)
  • Meilleurs moniteurs pour les programmes multithread (2007)
  • Comprendre la concurrence des acteurs, partie 1 (2009)
  • Détection et gestion des fils suspendus (2011)

Consultez également le plan du site et le moteur de recherche JavaWorld .

Les applications multithreads fournissent leur puissance puissante en exécutant plusieurs threads simultanément dans un seul programme. D'un point de vue logique, le multithreading signifie que plusieurs lignes d'un même programme peuvent être exécutées en même temps, cependant, ce n'est pas la même chose que de démarrer un programme deux fois et de dire qu'il y a plusieurs lignes d'un programme en cours d'exécution en même temps. temps. Dans ce cas, le système d'exploitation traite les programmes comme deux processus séparés et distincts. Sous Unix, forger un processus crée un processus enfant avec un espace d'adressage différent pour le code et les données. cependant,fork()crée beaucoup de surcharge pour le système d'exploitation, ce qui en fait une opération très gourmande en ressources processeur. En démarrant un thread à la place, un chemin d'exécution efficace est créé tout en partageant toujours la zone de données d'origine du parent. L'idée de partager la zone de données est très bénéfique, mais soulève certains domaines de préoccupation dont nous discuterons plus tard.

Créer des threads

Les créateurs de Java ont gracieusement conçu deux façons de créer des threads: implémenter une interface et étendre une classe. L'extension d'une classe est la manière dont Java hérite des méthodes et des variables d'une classe parent. Dans ce cas, on ne peut étendre ou hériter que d'une seule classe parente. Cette limitation dans Java peut être surmontée en implémentant des interfaces, qui est la manière la plus courante de créer des threads. (Notez que l'acte d'hériter permet simplement à la classe d'être exécutée en tant que thread. C'est à la classe de s'exécuter start(), etc.)

Les interfaces permettent aux programmeurs de jeter les bases d'une classe. Ils sont utilisés pour concevoir les exigences d'un ensemble de classes à implémenter. L'interface configure tout et la ou les classes qui implémentent l'interface font tout le travail. Les différents ensembles de classes qui implémentent l'interface doivent suivre les mêmes règles.

Il existe quelques différences entre une classe et une interface. Premièrement, une interface ne peut contenir que des méthodes abstraites et / ou des variables finales statiques (constantes). Les classes, en revanche, peuvent implémenter des méthodes et contenir des variables qui ne sont pas des constantes. Deuxièmement, une interface ne peut implémenter aucune méthode. Une classe qui implémente une interface doit implémenter toutes les méthodes définies dans cette interface. Une interface a la capacité de s'étendre à partir d'autres interfaces et (contrairement aux classes) peut s'étendre à partir de plusieurs interfaces. De plus, une interface ne peut pas être instanciée avec le nouvel opérateur; par exemple, Runnable a=new Runnable();n'est pas autorisé.

La première méthode de création d'un thread consiste simplement à s'étendre à partir de la Threadclasse. Ne faites cela que si la classe dont vous avez besoin exécutée en tant que thread n'a jamais besoin d'être étendue d'une autre classe. La Threadclasse est définie dans le package java.lang, qui doit être importé pour que nos classes connaissent sa définition.

import java.lang.*; public class Counter extends Thread { public void run() { .... } }

L'exemple ci-dessus crée une nouvelle classe Counterqui étend la Threadclasse et remplace la Thread.run()méthode pour sa propre implémentation. La run()méthode est l'endroit où tout le travail du Counterthread de classe est effectué. La même classe peut être créée en implémentant Runnable:

import java.lang.*; public class Counter implements Runnable { Thread T; public void run() { .... } }

Ici, la run()méthode abstraite est définie dans l'interface Runnable et est en cours d'implémentation. Notez que nous avons une instance de la Threadclasse comme variable de la Counterclasse. La seule différence entre les deux méthodes est qu'en implémentant Runnable, il y a une plus grande flexibilité dans la création de la classe Counter. Dans l'exemple ci-dessus, il est toujours possible d'étendre la Counterclasse, si nécessaire. La majorité des classes créées qui doivent être exécutées en tant que thread implémenteront Runnable car elles étendent probablement d'autres fonctionnalités d'une autre classe.

Ne pensez pas que l'interface Runnable effectue un travail réel lorsque le thread est en cours d'exécution. Il s'agit simplement d'une classe créée pour donner une idée de la conception de la Threadclasse. En fait, il est très petit et ne contient qu'une seule méthode abstraite. Voici la définition de l'interface Runnable directement depuis la source Java:

package java.lang; public interface Runnable { public abstract void run(); }

C'est tout ce qu'il y a dans l'interface Runnable. Une interface fournit uniquement une conception sur laquelle les classes doivent être implémentées. Dans le cas de l'interface Runnable, cela force la définition de la run()méthode uniquement . Par conséquent, la plupart du travail est effectué en Threadclasse. Un examen plus attentif d'une section de la définition de la Threadclasse donnera une idée de ce qui se passe réellement:

public class Thread implements Runnable { ... public void run() { if (target != null) { target.run(); } } ... }

D'après l'extrait de code ci-dessus, il est évident que la classe Thread implémente également l'interface Runnable. Thread. run()vérifie que la classe cible (la classe qui va être exécutée en tant que thread) n'est pas égale à null, puis exécute la run()méthode de la cible. Lorsque cela se produit, la run()méthode de la cible s'exécutera en tant que son propre thread.

Démarrage et arrêt

Puisque les différentes façons de créer une instance d'un thread sont maintenant apparentes, nous allons discuter de l'implémentation des threads en commençant par les moyens disponibles pour les démarrer et les arrêter à l'aide d'une petite applet contenant un thread pour illustrer la mécanique:

Exemple de CounterThread et code source

L'applet ci-dessus commencera à compter à partir de 0, affichant sa sortie à la fois à l'écran et à la console. Un rapide coup d'œil peut donner l'impression que le programme commencera à compter et affichera chaque nombre, mais ce n'est pas le cas. Un examen plus approfondi de l'exécution de cette applet révélera sa véritable identité.

In this case, the CounterThread class was forced to implement Runnable since it extended the class Applet. As in all applets, the init() method gets executed first. In init(), the variable Count is initialized to zero and a new instance of the Thread class is created. By passing this to the Thread constructor, the new thread will know which object to run. In this case this is a reference to CounterThread. After the thread is created it needs to be started. The call to start() will call the target's run() method, which is CounterThread.run(). The call to start() will return right away and the thread will start executing at the same time. Note that the run() method is an infinite loop. It is infinite because once the run() method exits, the thread stops executing. The run() method will increment the variable Count, sleep for 10 milliseconds and send a request to refresh the applet's display.

Note that it is important to sleep somewhere in a thread. If not, the thread will consume all CPU time for the process and will not allow any other methods such as threads to be executed. Another way to cease the execution of a thread is to call the stop() method. In this example, the thread stops when the mouse is pressed while the cursor is in the applet. Depending on the speed of the computer the applet runs on, not every number will be displayed, because the incrementing is done independent of the painting of the applet. The applet can not be refreshed at every request, so the OS will queue the requests and successive refresh requests will be satisfied with one refresh. While the refreshes are queuing up, the Count is still being incremented but not displayed.

Suspending and resuming

Once a thread is stopped, it cannot be restarted with the start() command, since stop() will terminate the execution of a thread. Instead you can pause the execution of a thread with the sleep() method. The thread will sleep for a certain period of time and then begin executing when the time limit is reached. But, this is not ideal if the thread needs to be started when a certain event occurs. In this case, the suspend() method allows a thread to temporarily cease executing and the resume() method allows the suspended thread to start again. The following applet shows the above example modified to suspend and resume the applet.

public class CounterThread2 extends Applet implements Runnable { Thread t; int Count; boolean suspended; public boolean mouseDown(Event e,int x, int y) { if(suspended) t.resume(); else t.suspend(); suspended = !suspended; return true; } ... }

CounterThread2 Example and Source code

Pour garder une trace de l'état actuel de l'applet, la variable booléenne suspendedest utilisée. Distinguer les différents états d'une applet est important car certaines méthodes lèveront des exceptions si elles sont appelées dans un état incorrect. Par exemple, si l'applet a été démarrée et arrêtée, l'exécution de la start()méthode lèvera une IllegalThreadStateExceptionexception.