in

Comportement des threads dans la JVM

Enfilage fait référence à la pratique consistant à exécuter simultanément des processus de programmation pour améliorer les performances des applications. Bien qu'il ne soit pas si courant de travailler avec des threads directement dans des applications métier, ils sont constamment utilisés dans les frameworks Java. À titre d'exemple, les frameworks qui traitent un grand volume d'informations 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.

Cet article vous présente quelques bases des threads Java traditionnels et de l'exécution des threads dans la machine virtuelle Java. Consultez l'introduction d'InfoWorld à Project Loom pour en savoir plus sur les threads virtuels et le nouveau modèle de concurrence structurée de Java.

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

Même si vous n'avez jamais travaillé directement avec des 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é le main() méthode, vous avez également exécuté la méthode principale Thread.

Étudier le Thread La classe est très utile pour comprendre le fonctionnement du threading dans les programmes Java. Nous pouvons accéder au thread en cours d'exécution en appelant le 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 « principal », identifiant le thread en cours d'exécution. Savoir comment identifier le thread en cours d'exécution est la première étape pour assimiler les concepts du thread.

Le cycle de vie des threads Java

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

  • Nouveau : Un nouveau Thread() a été instancié.
  • Exécutable : le Threadc'est start() La méthode a été invoquée.
  • Course à pied : le start() La méthode a été invoqué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à invoqué le synchronized() méthode et le thread suivant doit attendre qu'il soit terminé.
  • Terminé : l'exécution du thread est terminée.
Un diagramme montrant les six étapes du cycle de vie des threads Java. Rafael Chinelato Del Nero

Figure 1. Les six états du cycle de vie des threads Java

Il y a encore beaucoup à explorer et à comprendre sur les états des threads, mais les informations de la figure 1 sont suffisantes pour l'instant.

Extension d'une classe Thread

Dans sa forme la plus simple, le traitement simultané est effectué en étendant un Thread classe, comme indiqué ici :


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 MainThread et le InheritingThread. Quand on invoque le start() méthode avec le nouveau inheritingThread()la logique dans le run() la méthode est exécutée.

Nous passons également le nom du deuxième thread dans le Thread constructeur 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. Qui passe Runnable à l'intérieur d'un Thread Le constructeur entraîne moins de couplage et plus de flexibilité. Après avoir passé Runnableon peut invoquer le 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 ou démon

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

  • Threads non-démons sont exécutés jusqu’au bout. Le thread principal est un bon exemple de thread non-démon. Coder dans main() sera toujours exécuté jusqu'au bout, à moins qu'un System.exit() force le programme à se terminer.
  • UN fil de démon est le contraire, fondamentalement un processus qui n'a pas besoin d'être exécuté jusqu'à la fin.

Rappelez-vous la règle:Si un thread non démon englobant se termine avant un thread démon, le thread démon ne sera pas exécuté avant la fin.

Pour mieux comprendre la relation entre les threads démons et non-démons, é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 parcourir toutes, puis les imprimer. Mais rappelez-vous qu'un thread démon ne terminera pas son 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 que l'itération jusqu'à 100 000 ne soit terminée.

Le résultat final dépendra de votre implémentation JVM.

Comme vous pouvez le constater, les discussions sont imprévisibles.

Priorité des threads et JVM

Il est possible de prioriser l'exécution des threads avec le setPriority méthode, mais, encore une fois, la manière dont elle est gérée dépend de l'implémentation de la JVM. Linux, macOS et Windows ont tous des implémentations JVM différentes, et chacun gérera la priorité des threads selon les valeurs par défaut.

La priorité des threads que vous définissez influence toutefois l'ordre d'appel des threads. Les trois constantes déclarées dans le Thread la classe 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;

Essayez d'exécuter des tests sur le code suivant pour voir quelle priorité d'exécution vous obtenez :


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();
    }

}

Même si nous définissons moeThread comme MAX_PRIORITY, nous ne pouvons pas compter sur l'exécution de ce thread en premier. Au lieu de cela, l’ordre d’exécution sera aléatoire.

Une note sur les constantes et les énumérations

Le Thread La classe a été introduite avec la toute première version de Java. À cette époque, les priorités étaient définies à l’aide de constantes et non d’énumérations. Il y a cependant un problème avec l'utilisation de constantes : si nous transmettons un numéro de priorité qui n'est pas compris entre 1 et 10, le setPriority() La méthode lancera une IllegalArgumentException. Aujourd'hui, nous pouvons utiliser des énumérations pour contourner ce problème. L'utilisation d'énumérations rend impossible la transmission d'un argument illégal, ce qui simplifie le code et nous donne plus de contrôle sur son exécution.

Ce qu'il faut retenir des threads Java

  • Invoquer le start() méthode pour démarrer un Thread.
  • Il est possible de prolonger le Thread directement la classe afin d'utiliser les threads.
  • Il est possible d'implémenter une action de thread dans un Runnable interface.
  • La priorité des threads dépend de l'implémentation de la JVM.
  • Le comportement du thread dépend également de l’implémentation de la JVM.
  • Un thread démon ne se terminera pas si un thread non-démon englobant se termine en premier.

Erreurs courantes avec les threads Java

  • Invoquer le run() La méthode n’est pas le moyen de démarrer un nouveau fil de discussion.
  • Essayer de démarrer un fil de discussion deux fois entraînera un IllegalThreadStateException.
  • Évitez d'autoriser plusieurs processus à modifier l'état d'un objet.
  • N'écrivez pas de logique de programme qui repose sur la priorité des threads (vous ne pouvez pas la prédire).
  • Ne vous fiez pas à l'ordre d'exécution des threads : même si vous démarrez un thread en premier, rien ne garantit qu'il sera exécuté en premier.

Relevez le défi des threads Java !

Vous avez appris peu de choses sur les threads Java, alors essayons un défi Java pour tester ce que vous avez appris.


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());
            }
        }
    }
}

Selon vous, quel sera le résultat de ce code ? Voici les options :

A.Harley Davidson
B. Esquiver Tomahawk
C.Yamaha YZF
D. Indéterminé

Résoudre le défi

Dans le code ci-dessus, nous avons créé trois threads. Le premier fil est Harley Davidson, et nous avons attribué à ce fil la priorité par défaut. Le deuxième fil est Dodge Tomahawkattribué MAX_PRIORITY. Le troisième est Yamaha YZFavec MIN_PRIORITY. Ensuite, nous avons lancé les discussions.

Pour déterminer l'ordre dans lequel les threads s'exécuteront, vous pouvez d'abord noter que le Motorcycle la classe étend la Thread classe, et que nous avons passé le nom du thread dans le constructeur. Nous avons également remplacé le run() méthode avec une condition : if (wolverineAdrenaline == 13).

Même si Yamaha YZF est le troisième thread dans notre ordre d'exécution, et a MIN_PRIORITYrien ne garantit qu'il sera exécuté en dernier pour toutes les implémentations JVM.

Vous remarquerez peut-être également que dans cet exemple, nous définissons le Dodge Tomahawk fil comme daemon. Parce que c'est un thread démon, Dodge Tomahawk pourrait ne jamais achever son exécution. Mais les deux autres threads ne sont pas des démons par défaut, donc le Harley Davidson et Yamaha YZF les threads termineront certainement leur exécution.

Pour conclure, le résultat sera D : Indéterminé. En effet, il n'y a aucune garantie que le planificateur de threads suivra notre ordre d'exécution ou la priorité des threads.

N'oubliez pas que nous ne pouvons pas nous fier à la logique du programme (ordre des threads ou priorité des threads) pour prédire l'ordre d'exécution de la JVM.

Défi vidéo ! Arguments de variables de débogage

Le débogage est l’un des moyens les plus simples d’assimiler pleinement les concepts de programmation tout en améliorant votre code. Dans cette vidéo, vous pouvez suivre pendant que je débogue et explique le défi du comportement des threads :

En savoir plus sur Java

Copyright © 2019 IDG Communications, Inc.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

GIPHY App Key not set. Please check settings

    7 médailles, zéro en or pour le Cameroun aux 23ème championnats d’Afrique d’athlétisme

    Le Tour de France, révélateur d’un patrimoine géologique national