Comportement des threads dans la JVM

Le threading fait référence à la pratique consistant à exécuter des processus de programmation simultanément pour améliorer les performances des applications. Bien qu'il ne soit pas si courant de travailler avec des threads directement dans les applications métier, ils sont utilisés tout le temps dans les frameworks Java.

À titre d'exemple, les frameworks qui traitent un grand volume d'informations, comme Spring Batch, utilisent des threads pour gérer les données. La manipulation simultanée de threads ou de processus CPU améliore les performances, ce qui se traduit par des programmes plus rapides et plus efficaces.

Obtenez le code source

Obtenez le code de ce Java Challenger. Vous pouvez exécuter vos propres tests tout en suivant les exemples.

Trouvez votre premier thread: la méthode main () de Java

Même si vous n'avez jamais travaillé directement avec les threads Java, vous avez travaillé indirectement avec eux car la méthode main () de Java contient un thread principal. Chaque fois que vous avez exécuté la main()méthode, vous avez également exécuté le fichier main Thread.

L'étude de la Threadclasse est très utile pour comprendre le fonctionnement des threads dans les programmes Java. Nous pouvons accéder au thread en cours d'exécution en appelant la currentThread().getName()méthode, comme indiqué ici:

 public class MainThread { public static void main(String... mainThread) { System.out.println(Thread.currentThread().getName()); } } 

Ce code imprimera «main», identifiant le thread en cours d'exécution. Savoir comment identifier le thread en cours d'exécution est la première étape pour absorber les concepts de thread.

Le cycle de vie des threads Java

Lorsque vous travaillez avec des threads, il est essentiel de connaître l'état des threads. Le cycle de vie des threads Java comprend six états de thread:

  • Nouveau : un nouveau Thread()a été instancié.
  • Exécutable : Threadla start()méthode de a été appelée.
  • En cours d'exécution : la start()méthode a été appelée et le thread est en cours d'exécution.
  • Suspendu : le thread est temporairement suspendu et peut être repris par un autre thread.
  • Bloqué : le thread attend une opportunité de s'exécuter. Cela se produit lorsqu'un thread a déjà appelé la synchronized()méthode et que le thread suivant doit attendre la fin.
  • Terminé : l'exécution du thread est terminée.
Rafael Chinelato Del Nero

Il y a plus à explorer et à comprendre sur les états des threads, mais les informations de la figure 1 vous suffisent pour résoudre ce défi Java.

Traitement simultané: extension d'une classe Thread

Dans sa forme la plus simple, le traitement concurrent est effectué en étendant une Threadclasse, comme illustré ci-dessous.

 public class InheritingThread extends Thread { InheritingThread(String threadName) { super(threadName); } public static void main(String... inheriting) { System.out.println(Thread.currentThread().getName() + " is running"); new InheritingThread("inheritingThread").start(); } @Override public void run() { System.out.println(Thread.currentThread().getName() + " is running"); } } 

Ici, nous exécutons deux threads: le MainThreadet le InheritingThread. Lorsque nous appelons la start()méthode avec le nouveau inheritingThread(), la logique de la run()méthode est exécutée.

Nous transmettons également le nom du deuxième thread dans le Threadconstructeur de classe, donc la sortie sera:

 main is running. inheritingThread is running. 

L'interface Runnable

Plutôt que d'utiliser l'héritage, vous pouvez implémenter l'interface Runnable. Passer à l' Runnableintérieur d'un Threadconstructeur se traduit par moins de couplage et plus de flexibilité. Après avoir passé Runnable, nous pouvons invoquer la start()méthode exactement comme nous l'avons fait dans l'exemple précédent:

 public class RunnableThread implements Runnable { public static void main(String... runnableThread) { System.out.println(Thread.currentThread().getName()); new Thread(new RunnableThread()).start(); } @Override public void run() { System.out.println(Thread.currentThread().getName()); } } 

Threads non démon vs démon

En termes d'exécution, il existe deux types de threads:

  • Les threads non démon sont exécutés jusqu'à la fin. Le thread principal est un bon exemple de thread non démon. Le code main()entrant sera toujours exécuté jusqu'à la fin, sauf si a System.exit()force le programme à se terminer.
  • Un thread démon est le contraire, en gros un processus qui ne doit pas être exécuté jusqu'à la fin.

Souvenez-vous de la règle : si un thread non démon englobant se termine avant un thread démon, le thread démon ne sera exécuté qu'à la fin.

Pour mieux comprendre la relation entre les threads démon et non démon, étudiez cet exemple:

 import java.util.stream.IntStream; public class NonDaemonAndDaemonThread { public static void main(String... nonDaemonAndDaemon) throws InterruptedException { System.out.println("Starting the execution in the Thread " + Thread.currentThread().getName()); Thread daemonThread = new Thread(() -> IntStream.rangeClosed(1, 100000) .forEach(System.out::println)); daemonThread.setDaemon(true); daemonThread.start(); Thread.sleep(10); System.out.println("End of the execution in the Thread " + Thread.currentThread().getName()); } } 

Dans cet exemple, j'ai utilisé un thread démon pour déclarer une plage de 1 à 100 000, les itérer toutes, puis les imprimer. Mais rappelez-vous, un thread démon ne terminera pas l'exécution si le thread principal du non-démon se termine en premier.

La sortie se déroulera comme suit:

  1. Début de l'exécution dans le thread principal.
  2. Imprimez des nombres de 1 à éventuellement 100 000.
  3. Fin de l'exécution dans le thread principal, très probablement avant la fin de l'itération à 100 000.

La sortie finale dépendra de votre implémentation JVM.

Et cela m'amène à mon point suivant: les fils sont imprévisibles.

Priorité des threads et JVM

Il est possible de prioriser l'exécution des threads avec la setPriorityméthode, mais la manière dont elle est gérée dépend de l'implémentation JVM. Linux, MacOS et Windows ont tous des implémentations JVM différentes, et chacun gérera la priorité des threads en fonction de ses propres valeurs par défaut.

La priorité de thread que vous définissez influence cependant l'ordre d'appel des threads. Les trois constantes déclarées dans la Threadclasse sont:

 /** * The minimum priority that a thread can have. */ public static final int MIN_PRIORITY = 1; /** * The default priority that is assigned to a thread. */ public static final int NORM_PRIORITY = 5; /** * The maximum priority that a thread can have. */ public static final int MAX_PRIORITY = 10; 

Try running some tests on the following code to see what execution priority you end up with:

 public class ThreadPriority { public static void main(String... threadPriority) { Thread moeThread = new Thread(() -> System.out.println("Moe")); Thread barneyThread = new Thread(() -> System.out.println("Barney")); Thread homerThread = new Thread(() -> System.out.println("Homer")); moeThread.setPriority(Thread.MAX_PRIORITY); barneyThread.setPriority(Thread.NORM_PRIORITY); homerThread.setPriority(Thread.MIN_PRIORITY); homerThread.start(); barneyThread.start(); moeThread.start(); } } 

Even if we set moeThread as MAX_PRIORITY, we cannot count on this thread being executed first. Instead, the order of execution will be random.

Constants vs enums

The Thread class was introduced with Java 1.0. At that time, priorities were set using constants, not enums. There's a problem with using constants, however: if we pass a priority number that is not in the range of 1 to 10, the setPriority() method will throw an IllegalArgumentException. Today, we can use enums to get around this issue. Using enums makes it impossible to pass an illegal argument, which both simplifies the code and gives us more control over its execution.

Take the Java threads challenge!

You've learned just a little bit about threads, but it's enough for this post's Java challenge.

To start, study the following code:

 public class ThreadChallenge { private static int wolverineAdrenaline = 10; public static void main(String... doYourBest) { new Motorcycle("Harley Davidson").start(); Motorcycle fastBike = new Motorcycle("Dodge Tomahawk"); fastBike.setPriority(Thread.MAX_PRIORITY); fastBike.setDaemon(false); fastBike.start(); Motorcycle yamaha = new Motorcycle("Yamaha YZF"); yamaha.setPriority(Thread.MIN_PRIORITY); yamaha.start(); } static class Motorcycle extends Thread { Motorcycle(String bikeName) { super(bikeName); } @Override public void run() { wolverineAdrenaline++; if (wolverineAdrenaline == 13) { System.out.println(this.getName()); } } } } 

What will be the output of this code? Analyze the code and try to determine the answer for yourself, based on what you've learned.

A. Harley Davidson

B. Dodge Tomahawk

C. Yamaha YZF

D. Indeterminate

What just happened? Understanding threads behavior

In the above code, we created three threads. The first thread is Harley Davidson, and we assigned this thread the default priority. The second thread is Dodge Tomahawk, assigned MAX_PRIORITY. The third is Yamaha YZF, with MIN_PRIORITY. Then we started the threads.

In order to determine the order the threads will run in, you might first note that the Motorcycle class extends the Thread class, and that we've passed the thread name in the constructor. We've also overridden the run() method with a condition: if wolverineAdrenaline is equals to 13.

Even though Yamaha YZF is the third thread in our order of execution, and has MIN_PRIORITY, there's no guarantee that it will be executed last for all JVM implementations.

You might also note that in this example we set the Dodge Tomahawk thread as daemon. Because it's a daemon thread, Dodge Tomahawk may never complete execution. But the other two threads are non-daemon by default, so the Harley Davidson and Yamaha YZF threads will definitely complete their execution.

To conclude, the result will be D: Indeterminate, because there is no guarantee that the thread scheduler will follow our order of execution or thread priority.

Remember, we can't rely on program logic (order of threads or thread priority) to predict the JVM's order of execution.

Video challenge! Debugging variable arguments

Debugging is one of the easiest ways to fully absorb programming concepts while also improving your code. In this video you can follow along while I debug and explain the thread behavior challenge:

Common mistakes with Java threads

  • Invoking the run() method to try to start a new thread.
  • Trying to start a thread twice (this will cause an IllegalThreadStateException).
  • Allowing multiple processes to change the state of an object when it shouldn't change.
  • Writing program logic that relies on thread priority (you can't predict it).
  • Relying on the order of thread execution--even if we start a thread first, there is no guarantee it will be executed first.

What to remember about Java threads

  • Invoke the start() method to start a Thread.
  • It's possible to extend the Thread class directly in order to use threads.
  • It's possible to implement a thread action inside a Runnable interface.
  • Thread priority depends on the JVM implementation.
  • Thread behavior will always depend on the JVM implementation.
  • A daemon thread won't complete if an enclosing non-daemon thread ends first.

Learn more about Java threads on JavaWorld

  • Read the Java 101 threads series to learn more about threads and runnables, thread synchronization, thread scheduling with wait/notify, and thread death.
  • Modern threading: A Java concurrency primer introduces java.util.concurrent and answers common questions for developers new to Java concurrency.
  • Modern threading for not-quite-beginners offers more advanced tips and best practices for working with java.util.concurrent.

More from Rafael

  • Get more quick code tips: Read all the posts in the Java Challengers series.
  • Build your Java skills: Visit the Java Dev Gym for a code workout.
  • Want to work on stress free projects and write bug-free code? Visit the NoBugsProject for your copy of No Bugs, No Stress - Create a Life-Changing Software Without Destroying Your Life.

Cette histoire, "Comportement des threads dans la JVM" a été publiée à l'origine par JavaWorld.