W3docs

Méthodes des threads Java

Les principales méthodes de contrôle des threads Java — start, run, sleep, join, interrupt, yield, setDaemon — avec les pièges courants.

Thread possède de nombreuses méthodes, mais seule une poignée est réellement utilisée dans du code réel. Ce chapitre passe en revue cette poignée — ce que chacune fait, ce qu'elle ne fait pas, et les erreurs qui semblent correctes jusqu'à ce qu'elles ne le soient plus. Les chapitres précédents les ont introduites en passant ; ici, elles sont examinées individuellement.

Cette page couvre le cycle de vie et le contrôle des threads : start vs run, sleep, join, le protocole d'annulation coopérative interrupt, ainsi que les petits utilitaires (yield, currentThread, nommage, statut daemon, priorité, holdsLock, onSpinWait). Pour une vue d'ensemble de la façon dont un thread se déplace entre NEW, RUNNABLE, BLOCKED, WAITING et TERMINATED, consultez Cycle de vie des threads Java.

start() vs. run()

Le bug de multithreading le plus courant :

Thread t = new Thread(() -> work(), "worker");
t.run();                                      // wrong: runs work() on the CURRENT thread
t.start();                                    // right: spawns a new OS thread, returns immediately

start() est la seule méthode qui crée un nouveau thread OS. run() est le corps du travail — l'appeler directement est simplement une invocation de méthode normale qui retourne lorsque le travail est terminé. Si vous ne voyez pas start(), aucun parallélisme n'a lieu.

start() est également à usage unique. Après le retour de run(), le thread est TERMINATED et ne peut pas être redémarré. Un deuxième appel à start() lève une IllegalThreadStateException.

Thread.sleep(ms)

L'appel statique qui met en attente le thread courant pendant au moins la durée donnée :

Thread.sleep(1500);                          // sleep 1.5 seconds
Thread.sleep(0, 250);                        // 250 nanoseconds; precision varies by OS
Thread.sleep(Duration.ofMillis(1500));       // Java 19+ overload

Trois choses à savoir :

  • Il lève InterruptedException. Le sommeil est interruptible — c'est ainsi qu'un worker est informé d'arrêter de dormir et de s'arrêter. Vous propagez soit l'exception (déclarez throws), soit vous la capturez et réactivez le drapeau avec Thread.currentThread().interrupt().
  • Il ne libère pas les verrous. Un thread en sommeil conserve tous les verrous qu'il détenait auparavant. Si vous appelez Thread.sleep à l'intérieur d'un bloc synchronized, aucun autre thread n'entre dans le bloc pendant que vous dormez. C'est presque toujours un bug ; utilisez wait ou Condition.await (voir Communication inter-thread) quand vous devez libérer le verrou.
  • Le timing est "au moins", pas "exactement". L'OS peut vous réveiller un peu en retard sous charge ; il ne vous réveille jamais en avance.

t.join() et t.join(ms)

Attendre qu'un autre thread se termine :

t.join();                                    // block until t terminates
t.join(2000);                                // block up to 2 seconds, then continue regardless
boolean done = t.join(Duration.ofSeconds(2));// Java 19+, returns whether it finished

join est la façon de composer un travail parallèle en plusieurs étapes : lancez quelques threads, laissez-les s'exécuter, faites un join sur tous, lisez leurs résultats. join() retourne quand le run() du thread cible a retourné (normalement ou par exception). Il lève également InterruptedException afin que les appelants puissent être interrompus pendant l'attente.

Un point subtil : join(0) signifie "join sans timeout" (c'est-à-dire attendre indéfiniment), et non "join avec un timeout de zéro". Si vous voulez un vrai appel "abandonner immédiatement", utilisez t.isAlive() à la place.

t.interrupt() et le drapeau

Le protocole d'annulation coopérative, en trois appels :

t.interrupt();                               // set t's interrupt flag (and unblock sleep/wait/join/park)
t.isInterrupted();                           // ask whether the flag is set (does NOT clear)
Thread.interrupted();                        // static; ask current thread, and CLEAR the flag

Le drapeau n'est qu'un volatile boolean sur l'objet Thread. interrupt() le positionne. Si t est actuellement dans sleep, wait, join, ou LockSupport.park (ou de nombreux appels bloquants java.nio), cet appel bloquant lève immédiatement InterruptedException. Sinon, le drapeau attend que le worker le remarque par lui-même.

Un worker qui veut être interruptible a deux responsabilités :

  1. Vérifier Thread.currentThread().isInterrupted() entre les étapes longues.
  2. Dans chaque catch (InterruptedException e), soit propager, soit réarmer le drapeau avec Thread.currentThread().interrupt() — ne jamais l'avaler silencieusement.
while (!Thread.currentThread().isInterrupted()) {
  try {
    doOneUnit();
    Thread.sleep(100);
  } catch (InterruptedException e) {
    Thread.currentThread().interrupt();        // restore flag for the loop check
  }
}

Thread.yield() — presque jamais le bon outil

Thread.yield();                              // hint: please run someone else

Un conseil non contraignant pour le planificateur. L'OS est libre de l'ignorer. Il n'existe pratiquement aucun code de production qui ait besoin de yield — si vous voulez attendre un événement, utilisez un wait, un Condition, ou un Semaphore. Recourez à yield uniquement pour les micro-benchmarks, les harnais de test de deadlock, ou si vous écrivez la JVM elle-même.

Thread.currentThread()

L'accesseur statique pour le thread sur lequel le code appelant s'exécute. Les deux usages que vous rencontrerez :

String who = Thread.currentThread().getName();
Thread.currentThread().interrupt();          // re-arm the flag after catching InterruptedException

getName() est également la façon standard d'étiqueter les lignes de journal pour distinguer les threads dans la sortie de production.

getName / setName

Les noms sont importants pour le débogage. Le nom par défaut (Thread-3) est inutile dans un thread dump.

Thread t = new Thread(this::flush, "flush-loop");      // name at construction (preferred)
t.setName("flush-loop-2");                              // rename later if a role changes

Vous pouvez renommer à tout moment, mais la valeur au moment du dump ou du log est ce que le lecteur verra. Passez toujours un nom au constructeur.

setDaemon(true)

Thread t = new Thread(this::poll, "metrics-poller");
t.setDaemon(true);                           // BEFORE start(); else IllegalThreadStateException
t.start();

Les threads daemon ne maintiennent pas la JVM en vie — quand le dernier thread non-daemon se termine, la JVM interrompt brutalement les daemons. Utilisez-les pour les tâches d'entretien qui doivent mourir avec le programme (timers, videurs de métriques, boucles de polling). Ne les utilisez pas pour du travail dont vous avez réellement besoin de la complétion.

setPriority(int)

t.setPriority(Thread.MAX_PRIORITY);          // 10
t.setPriority(Thread.MIN_PRIORITY);          // 1
t.setPriority(Thread.NORM_PRIORITY);         // 5 (default)

Principalement indicatif. Le prochain chapitre couvre les priorités en détail ; pour l'instant, l'essentiel : ne vous fiez pas à elles pour l'exactitude, c'est l'OS qui décide de leur signification.

Thread.holdsLock(obj)

Un utilitaire de débogage statique :

assert Thread.holdsLock(monitor) : "expected to be inside a synchronized block on monitor";

Retourne true si le thread appelant détient le moniteur intrinsèque de obj. Utile pour affirmer "cette méthode ne doit être appelée que depuis l'intérieur d'un bloc synchronized" sans payer le coût d'acquisition du verrou sur le chemin normal.

Thread.onSpinWait() — Java 9+

while (!done) {
  Thread.onSpinWait();                       // hint to the CPU: I'm spinning, slow down
}

Un conseil au niveau CPU qui met en pause les pipelines et réduit la consommation d'énergie pendant une boucle de spin serrée. C'est spécifiquement pour le cas très étroit où vous attendez quelques microsecondes qu'un autre thread bascule un drapeau ; ce n'est pas un appel général "céder le CPU". Pour tout ce qui est plus long, utilisez LockSupport.park ou un Condition.

Un exemple concret : la plupart de ces méthodes en un seul endroit

Le programme ci-dessous utilise start, join avec un timeout, interrupt, sleep, isInterrupted et setName ensemble — les méthodes que vous appelleriez réellement en production.

java— editable, runs on the server

Ce qu'il faut retenir de l'exécution :

  • La ligne bad.run() a affiché ran on: main. Aucun nouveau thread n'a été créé. bad.isAlive() était false ensuite car start() n'a jamais été appelé. Tout programme multithread a ce bug à un moment donné ; une fois que vous l'avez fait, vous ne le refaites plus jamais.
  • slow.join(300) a retourné après environ 300 ms même si slow aurait dormi pendant 2000. isAlive() était encore true. join(ms) est l'attente bornée — utile quand vous voulez donner à un worker une chance gracieuse de terminer avant d'escalader.
  • slow.interrupt() a mis fin à son Thread.sleep immédiatement en lançant InterruptedException à l'intérieur du worker. C'est le contrat : les appels bloquants interruptibles réagissent à interrupt() en sortant avec l'exception, c'est ainsi que fonctionne l'annulation coopérative en pratique.
  • Le worker bookkeeper a capturé InterruptedException et réarmé le drapeau avec Thread.currentThread().interrupt(). Le isInterrupted() suivant a retourné true. Sans ce réarmement, le drapeau est perdu et tout code plus haut dans la pile d'appels pense qu'aucune interruption n'a eu lieu.
  • daemon.setDaemon(true) a été appelé avant start() — l'appeler après aurait lancé IllegalThreadStateException. Et quand main a retourné, le daemon a été tué en plein sommeil ; la JVM s'est arrêtée car il ne restait aucun thread non-daemon. C'est le compromis du daemon : ne bloque jamais la sortie de la JVM, n'est jamais garanti de se terminer.

Et ensuite

Le prochain chapitre, Priorité des threads Java, couvre la méthode setPriority sur Thread, ce que les priorités font réellement sur des OS réels, et pourquoi vous devriez les traiter comme un conseil plutôt qu'une garantie.

Pratique

Pratique
Vous capturez `InterruptedException` dans un worker mais ne voulez pas sortir de la boucle. Que devez-vous faire avec le drapeau d'interruption ?
Vous capturez `InterruptedException` dans un worker mais ne voulez pas sortir de la boucle. Que devez-vous faire avec le drapeau d'interruption ?
Was this page helpful?