Cycle de vie d'un thread en Java

1. Introduction

Dans cet article, nous aborderons en détail un concept de base en Java - le cycle de vie d'un thread.

Nous utiliserons un diagramme illustré rapide et, bien sûr, des extraits de code pratiques pour mieux comprendre ces états lors de l'exécution du thread.

Pour commencer à comprendre les threads en Java, cet article sur la création d'un thread est un bon point de départ.

2. Multithreading en Java

Dans le langage Java, le multithreading est piloté par le concept de base d'un Thread . Au cours de leur cycle de vie, les threads passent par différents états:

3. Cycle de vie d'un thread en Java

La classe java.lang.Thread contient une énumération State statique - qui définit ses états potentiels. À un moment donné, le thread ne peut être que dans l'un de ces états:

  1. NOUVEAU - un thread nouvellement créé qui n'a pas encore démarré l'exécution
  2. RUNNABLE - en cours d'exécution ou prêt pour l'exécution mais il attend l'allocation de ressources
  3. BLOQUÉ - en attente d'acquérir un verrouillage du moniteur pour entrer ou entrer de nouveau dans un bloc / méthode synchronisé
  4. EN ATTENTE - attendre qu'un autre thread exécute une action particulière sans aucune limite de temps
  5. TIMED_WAITING - en attente d'un autre thread pour effectuer une action spécifique pendant une période spécifiée
  6. TERMINÉ - a terminé son exécution

Tous ces états sont couverts dans le diagramme ci-dessus; discutons maintenant de chacun d'eux en détail.

3.1. Nouveau

Un NOUVEAU fil (ou un fil né ) est un fil qui a été créé mais pas encore démarré. Il reste dans cet état jusqu'à ce que nous le démarrions en utilisant la méthode start () .

L'extrait de code suivant montre un thread nouvellement créé qui est dans l' état NEW :

Runnable runnable = new NewState(); Thread t = new Thread(runnable); Log.info(t.getState());

Puisque nous n'avons pas démarré le thread mentionné, la méthode t.getState () imprime:

NEW

3.2. Runnable

Lorsque nous avons créé un nouveau thread et appelé la méthode start () à ce sujet, il est passé de l'état NEW à RUNNABLE . Les threads dans cet état sont en cours d'exécution ou prêts à fonctionner, mais ils attendent l'allocation de ressources du système.

Dans un environnement multi-thread, le Thread-Scheduler (qui fait partie de JVM) alloue une durée fixe à chaque thread. Ainsi, il s'exécute pendant un certain temps, puis abandonne le contrôle à d'autres threads RUNNABLE .

Par exemple, ajoutons la méthode t.start () à notre code précédent et essayons d'accéder à son état actuel:

Runnable runnable = new NewState(); Thread t = new Thread(runnable); t.start(); Log.info(t.getState());

Ce code est le plus susceptible de renvoyer la sortie sous la forme:

RUNNABLE

Notez que dans cet exemple, il n'est pas toujours garanti qu'au moment où notre contrôle atteindra t.getState () , il sera toujours dans l' état RUNNABLE .

Il peut arriver qu'il ait été immédiatement planifié par le Thread-Scheduler et peut finir son exécution. Dans de tels cas, nous pouvons obtenir une sortie différente.

3.3. Bloqué

Un thread est à l' état BLOQUÉ lorsqu'il n'est actuellement pas éligible pour s'exécuter. Il entre dans cet état lorsqu'il attend un verrouillage du moniteur et tente d'accéder à une section de code qui est verrouillée par un autre thread.

Essayons de reproduire cet état:

public class BlockedState { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new DemoThreadB()); Thread t2 = new Thread(new DemoThreadB()); t1.start(); t2.start(); Thread.sleep(1000); Log.info(t2.getState()); System.exit(0); } } class DemoThreadB implements Runnable { @Override public void run() { commonResource(); } public static synchronized void commonResource() { while(true) { // Infinite loop to mimic heavy processing // 't1' won't leave this method // when 't2' try to enter this } } }

Dans ce code:

  1. Nous avons créé deux threads différents - t1 et t2
  2. t1 démarre et entre la méthode synchronisée commonResource () ; cela signifie qu'un seul thread peut y accéder; tous les autres threads suivants qui tentent d'accéder à cette méthode seront bloqués de la suite de l'exécution jusqu'à ce que l'actuel termine le traitement
  3. Lorsque t1 entre dans cette méthode, il est conservé dans une boucle while infinie; c'est juste pour imiter un traitement lourd afin que tous les autres threads ne puissent pas entrer dans cette méthode
  4. Maintenant, lorsque nous démarrons t2 , il essaie d'entrer dans la méthode commonResource () , qui est déjà accédée par t1, ainsi, t2 sera maintenu dans l' état BLOQUÉ

Étant dans cet état, nous appelons t2.getState () et obtenons la sortie comme:

BLOCKED

3.4. Attendre

Un thread est à l' état WAITING lorsqu'il attend qu'un autre thread exécute une action particulière. Selon JavaDocs, n'importe quel thread peut entrer dans cet état en appelant l'une des trois méthodes suivantes:

  1. object.wait ()
  2. thread.join () ou
  3. LockSupport.park ()

Notez que dans wait () et join () - nous ne définissons aucun délai d'expiration car ce scénario est couvert dans la section suivante.

Nous avons un tutoriel séparé qui traite en détail de l'utilisation de wait () , notify () et notifyAll () .

Pour l'instant, essayons de reproduire cet état:

public class WaitingState implements Runnable { public static Thread t1; public static void main(String[] args) { t1 = new Thread(new WaitingState()); t1.start(); } public void run() { Thread t2 = new Thread(new DemoThreadWS()); t2.start(); try { t2.join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); Log.error("Thread interrupted", e); } } } class DemoThreadWS implements Runnable { public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); Log.error("Thread interrupted", e); } Log.info(WaitingState.t1.getState()); } }

Let's discuss what we're doing here:

  1. We've created and started the t1
  2. t1 creates a t2 and starts it
  3. While the processing of t2 continues, we call t2.join(), this puts t1 in WAITING state until t2 has finished execution
  4. Since t1 is waiting for t2 to complete, we're calling t1.getState() from t2

The output here is, as you'd expect:

WAITING

3.5. Timed Waiting

A thread is in TIMED_WAITING state when it's waiting for another thread to perform a particular action within a stipulated amount of time.

According to JavaDocs, there are five ways to put a thread on TIMED_WAITING state:

  1. thread.sleep(long millis)
  2. wait(int timeout) or wait(int timeout, int nanos)
  3. thread.join(long millis)
  4. LockSupport.parkNanos
  5. LockSupport.parkUntil

To read more about the differences between wait() and sleep() in Java, have a look at this dedicated article here.

For now, let's try to quickly reproduce this state:

public class TimedWaitingState { public static void main(String[] args) throws InterruptedException { DemoThread obj1 = new DemoThread(); Thread t1 = new Thread(obj1); t1.start(); // The following sleep will give enough time for ThreadScheduler // to start processing of thread t1 Thread.sleep(1000); Log.info(t1.getState()); } } class DemoThread implements Runnable { @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); Log.error("Thread interrupted", e); } } }

Here, we've created and started a thread t1 which is entered into the sleep state with a timeout period of 5 seconds; the output will be:

TIMED_WAITING

3.6. Terminated

This is the state of a dead thread. It's in the TERMINATED state when it has either finished execution or was terminated abnormally.

We have a dedicated article that discusses different ways of stopping the thread.

Let's try to achieve this state in the following example:

public class TerminatedState implements Runnable { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new TerminatedState()); t1.start(); // The following sleep method will give enough time for // thread t1 to complete Thread.sleep(1000); Log.info(t1.getState()); } @Override public void run() { // No processing in this block } }

Here, while we've started thread t1, the very next statement Thread.sleep(1000) gives enough time for t1 to complete and so this program gives us the output as:

TERMINATED

In addition to the thread state, we can check the isAlive() method to determine if the thread is alive or not. For instance, if we call the isAlive() method on this thread:

Assert.assertFalse(t1.isAlive());

Il renvoie faux. En termes simples, un thread est vivant si et seulement s'il a été démarré et n'est pas encore mort.

4. Conclusion

Dans ce didacticiel, nous avons découvert le cycle de vie d'un thread en Java. Nous avons examiné les six états définis par Thread.State enum et les avons reproduits avec des exemples rapides.

Bien que les extraits de code donnent la même sortie dans presque toutes les machines, dans certains cas exceptionnels, nous pouvons obtenir des sorties différentes car le comportement exact de Thread Scheduler ne peut pas être déterminé.

Et, comme toujours, les extraits de code utilisés ici sont disponibles sur GitHub.