• Home
  • LLMs
  • Python
  • Docker
  • Kubernetes
  • Java
  • Maven
  • All
  • About
Java | Threads
  1. Introduction
  2. Définir un thread
    1. Étendre la classe Thread
    2. Implémenter l'interface Runnable
  3. Instancier un thread
  4. Démarrer un thread
  5. États d'un thread à l'exécution

  1. Introduction
    Dans un thread d'exécution, les instructions d'un programme sont exécutées séquentiellement dans l'ordre dans lequel elles étaient codées.

    Le thread utilise une pile d'exécution (call stack) pour séquencer l'exécution des instructions du programme.
    Il n'est pas possible dans un thread d'exécuter les instructions d'un programme en parallèle.
    Le seul moyen pour rouler des programmes en parallèle est de créer de nouveaux threads.
    Chaque thread aura sa propre pile d'exécution et s'occupera d'exécuter la tâche qui lui a été confiée.

    En Java, chaque application, exécutée par la JVM, obtient un thread par défaut appelé le thread principal (the main thread).

    Il faut distinguer deux choses différentes lorsqu'on parle des threads :
    • le code à exécuter,
    • et le thread (instance de la classe Thread) qui va exécuter ce code.

    Java offre deux moyens pour définir un nouveau thread d'exécution :
    • en étendant la classe Thread,
    • ou en implémentant l'interface Runnable.

    Dans les deux cas il faut fournir une implémentation de la méthode run() qui doit contenir le code à exécuter par le thread.
    Il faut noter que la classe Thread implémente l'interface Runnable qui définie la méthode run().
  2. Définir un thread
    1. Étendre la classe Thread
      public class MyThread extends Thread {
          @Override
          public void run() {
              System.out.println("Thread: " + Thread.currentThread().getName() + " (" + Thread.currentThread().threadId() + ")");
          }
      }
    2. Implémenter l'interface Runnable
      public class MyRunnable implements Runnable {
          @Override
          public void run() {
              System.out.println("Runnable: " + Thread.currentThread().getName() + " (" + Thread.currentThread().threadId() + ")");
          }
      }
      Il est recommandé d'implémenter l'interface Runnable, plutôt qu'étendre la classe Thread, car cela permet d'avoir une bonne séparation entre le thread (l'instance) et le code à exécuté par ce thread (voir section suivante).

      Généralement, la redéfinition de la classe Thread est réservée aux cas où on veut étendre ou modifier le comportement de la classe Thread.
  3. Instancier un thread
    L'instanciation d'un thread est l'opération par laquelle on crée une instance de la classe Thread (ou une sous classe de la classe Thread).
    Cette instance est liée au code que le thread va exécuter (par la méthode run()). À cette étape, seule l'instance de la classe Thread est créée mais le thread d'exécution n'est pas encore démarré.

    Java offre plusieurs constructeurs qui peuvent être utilisés pour instancier un thread :
    • Instancier le thread en utilisant une sous-classe de la classe Thread.
      public class MyTest1 {
          Thread myThread = new MyThread();
      }
    • Instancier le thread en utilisant une classe qui implémente l'interface Runnable.
      public class MyTest1 {
          Runnable myRunnable =  new MyRunnable();
          Thread myThread = new Thread(myRunnable);
      }
    On peut utiliser une classe anonyme pour re-définir la classe Thread ou implémenter l'interface Runnable lors de l'instanciation du thread :
    public class MyTest1 {
        Thread myThread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Anonymous Runnable: " + Thread.currentThread().getName() + " (" + Thread.currentThread().threadId() + ")");
            }
        });
    }
  4. Démarrer un thread
    Démarrer un thread correspond à démarrer le thread d'exécution et lui associé une pile d'exécution et ainsi la JVM peut commencer à exécuter le code de la méthode run() de ce thread.

    Pour le faire, il faut appeler la méthode start() de l'instance du thread.
    public class MyTest1 {
        public static void main(String[] args) {
            Runnable myRunnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println("Anonymous Runnable: " + Thread.currentThread().getName() + " (" + Thread.currentThread().threadId() + ")");
                }
            };
    
            Thread myThread = new Thread(myRunnable, "myFirstThread");
    
            myThread.start();
        }
    }
    Notes :
    • Il est important de noter que l'instance du thread reste un objet comme n'import quel autre objet et qu'il est possible d'invoquer ses méthodes, y compris la méthode run() dont l'appel va comme même provoquer l'exécution du code de cette méthode, mais en aucun cas cet appel va lancer un nouveau thread d'exécution.
      public class MyTest1 {
          public static void main(String[] args) {
              Runnable myRunnable = new Runnable() {
                  @Override
                  public void run() {
                      System.out.println("Anonymous Runnable: " + Thread.currentThread().getName() + " (" + Thread.currentThread().threadId() + ")");
                  }
              };
      
              Thread myThread = new Thread(myRunnable, "myFirstThread");
      
              myThread.start();
      
              myThread.run();
          }
      }
      Résultat d'exécution du code :
      Anonymous Runnable: myFirstThread (9)
      Anonymous Runnable: main (1)
      Comme vous pouvez le remarquer l'appel de la méthode run() a été effectué dans le thread courant, dans notre cas le thread Main.
      Au contraire de l'appel de l'appel de la méthode start() qui créé un nouveau thread d'exécution qui s'occupe d'appeler implicitement la méthode run().

    • Donc seul l'appel de la méthode start() peut démarrer un nouveau thread d'exécution.
      Mais il faut faire attention que cet appel est possible une seul fois pour une instance d'un thread.
      Si un autre appel est fait, la JVM va lancer une erreur à l'exécution.
      package com.mtitek.threads;
      
      public class MyTest1 {
          public static void main(String[] args) {
              Runnable myRunnable = new Runnable() {
                  @Override
                  public void run() {
                      System.out.println("Anonymous Runnable: " + Thread.currentThread().getName() + " (" + Thread.currentThread().threadId() + ")");
                  }
              };
      
              Thread myThread = new Thread(myRunnable, "myFirstThread");
      
              myThread.start();
      
              myThread.start();
          }
      }
      Résultat d'exécution du code :
      Anonymous Runnable: myFirstThread (9)
      Exception in thread "main" java.lang.IllegalThreadStateException
          at java.lang.Thread.start(Thread.java:638)
          at com.mtitek.threads.MyTest1.main(MyTest1.java:16)
    • Si un code doit être exécuté plusieurs fois par différends threads d'exécution, il faut créer plusieurs instances du thread et appeler la méthode start() pour chacune de ces instances.
      public class MyTest1 {
          public static void main(String[] args) {
              Runnable myRunnable = new Runnable() {
                  @Override
                  public void run() {
                      System.out.println("Anonymous Runnable: " + Thread.currentThread().getName() + " (" + Thread.currentThread().threadId() + ")");
                  }
              };
      
              Thread myThread1 = new Thread(myRunnable, "myFirstThread");
              Thread myThread2 = new Thread(myRunnable, "mySecondThread");
              Thread myThread3 = new Thread(myRunnable, "myThirdThread");
      
              myThread1.start();
              myThread2.start();
              myThread3.start();
          }
      }
      Résultat d'exécution du code :
      Anonymous Runnable: myFirstThread (9)
      Anonymous Runnable: mySecondThread (10)
      Anonymous Runnable: myThirdThread (11)
    • L'ordre d'exécution des threads actifs n'est pas garanti.
      Java offre, cependant, des moyens pour solliciter la JVM à prioriser certains threads, mais, encore une fois, ça reste non garanti.

      Java offre aussi la possibilité de créer des dépendances entre des threads, et ainsi forcer la JVM à exécuter un thread après la fin de l'exécution du thread dont il dépend (plus de détails lorsque je vais présenter la méthode join()).
  5. États d'un thread à l'exécution
    Un thread peut avoir cinq états :
    • Instancié (NEW) :
      Une instance du thread a été créée, mais le thread n'a pas encore été encore démarré.

    • Prêt à exécuté (RUNNABLE) :
      La méthode start() a été appelée,
      Le thread n'est pas dans l'état "en attente d'exécution", mais le code de la méthode run() n'est pas en exécution par la JVM.

    • En exécution (RUNNING) :
      Le code de la méthode run() est en exécution par la JVM.

    • En attente d'exécution (BLOCKED, WAITING, TIMED_WAITING) :
      Le thread est dans cet état par ce qu'il a été endormi, bloqué, ou en attente d'un autre thread.

    • Terminé (TERMINATED) :
      La méthode run() a terminée son exécution.

    public class ThreadStateTest {
        public static void main(String[] args) {
            Runnable myRunnable = new Runnable() {
                @Override
                public void run() {
                    System.out.println("(1) " + Thread.currentThread().getState());  // RUNNABLE
    
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
    
            Thread myThread1 = new Thread(myRunnable, "myJoinThread");
    
            System.out.println("(2) " + myThread1.getState()); // NEW
    
            myThread1.start();
    
            System.out.println("(3) " + myThread1.getState()); // RUNNABLE
    
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println("(4) " + myThread1.getState()); // TIMED_WAITING
    
            try {
                myThread1.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println("(5) " + myThread1.getState()); // TERMINATED
        }
    }
    Résultat d'exécution du code :
    (2) NEW
    (3) RUNNABLE
    (1) RUNNABLE
    (4) TIMED_WAITING
    (5) TERMINATED
© 2025  mtitek