JavaRush /Blog Java /Random-FR /Multithreading en Java : essence, avantages et pièges cou...

Multithreading en Java : essence, avantages et pièges courants

Publié dans le groupe Random-FR
Bonjour! Tout d’abord, félicitations : vous avez atteint le sujet du multithreading en Java ! Il s’agit d’une réalisation importante, il reste encore un long chemin à parcourir. Mais préparez-vous : c'est l'un des sujets les plus difficiles du cours. Et le fait n'est pas que des classes complexes ou de nombreuses méthodes soient utilisées ici : au contraire, il n'y en a même pas deux douzaines. C'est plutôt que vous devez changer un peu votre façon de penser. Auparavant, vos programmes étaient exécutés séquentiellement. Certaines lignes de code en suivaient d’autres, certaines méthodes en suivaient d’autres, et globalement tout était clair. Commencez par calculer quelque chose, puis affichez le résultat sur la console, puis terminez le programme. Pour comprendre le multithreading, il est préférable de penser en termes de concurrence. Commençons par quelque chose de très simple :) Multithreading en Java : essence, avantages et pièges courants - 1Imaginez que votre famille déménage d'une maison à une autre. Une partie importante du déménagement consiste à emballer vos livres. Vous avez accumulé beaucoup de livres et vous devez les mettre dans des cartons. Maintenant, vous seul êtes libre. Maman prépare à manger, son frère récupère des vêtements et sa sœur est allée au magasin. Seul, vous pouvez au moins vous débrouiller et, tôt ou tard, vous terminerez même la tâche vous-même, mais cela prendra beaucoup de temps. Cependant, dans 20 minutes, votre sœur reviendra du magasin et elle n'a rien d'autre à faire. Pour qu'elle puisse vous rejoindre. La tâche restait la même : mettre les livres dans des boîtes. Il court simplement deux fois plus vite. Pourquoi? Parce que le travail se fait en parallèle. Deux « threads » différents (vous et votre sœur) accomplissez simultanément la même tâche et, si rien ne change, le décalage horaire sera très important par rapport à une situation dans laquelle vous feriez tout seul. Si votre frère termine bientôt sa tâche, il pourra vous aider et les choses iront encore plus vite.

Problèmes que le multithreading résout en Java

Essentiellement, le multithreading Java a été inventé pour résoudre deux problèmes principaux :
  1. Effectuez plusieurs actions en même temps.

    Dans l'exemple ci-dessus, différents threads (c'est-à-dire des membres de la famille) ont effectué plusieurs actions en parallèle : faire la vaisselle, aller au magasin, plier des objets.

    Un exemple plus « programmeur » peut être donné. Imaginez que vous disposez d'un programme avec une interface utilisateur. Lorsque vous cliquez sur le bouton Continuer, certains calculs devraient avoir lieu dans le programme et l'utilisateur devrait voir l'écran d'interface suivant. Si ces actions sont effectuées séquentiellement, après avoir cliqué sur le bouton « Continuer », le programme se bloquera simplement. L'utilisateur verra le même écran avec un bouton « Continuer » jusqu'à ce que tous les calculs internes soient terminés et que le programme atteigne la partie où l'interface commencera à être dessinée.

    Eh bien, attendons quelques minutes !

    Multithreading en Java : essence, avantages et pièges courants - 3

    Nous pouvons également refaire notre programme ou, comme disent les programmeurs, le « paralléliser ». Laissez les calculs nécessaires être effectués dans un thread et le rendu de l'interface dans un autre. La plupart des ordinateurs disposent de suffisamment de ressources pour cela. Dans ce cas, le programme ne sera pas « stupide » et l'utilisateur se déplacera sereinement entre les écrans de l'interface sans se soucier de ce qui se passe à l'intérieur. Cela ne gêne pas :)

  2. Accélérez les calculs.

    Tout est beaucoup plus simple ici. Si notre processeur a plusieurs cœurs et que la plupart des processeurs sont désormais multicœurs, notre liste de tâches peut être résolue en parallèle par plusieurs cœurs. Évidemment, si nous devons résoudre 1 000 problèmes et que chacun d'eux est résolu en une seconde, un cœur traitera la liste en 1 000 secondes, deux cœurs en 500 secondes, trois en un peu plus de 333 secondes, et ainsi de suite.

Mais, comme vous l'avez déjà lu dans la conférence, les systèmes modernes sont très intelligents et même sur un seul cœur de calcul, ils sont capables de mettre en œuvre le parallélisme, ou pseudo-parallélisme, lorsque les tâches sont exécutées en alternance. Passons des choses générales aux choses spécifiques et familiarisons-nous avec la classe principale de la bibliothèque Java liée au multithreading - java.lang.Thread. À proprement parler, les threads en Java sont représentés par des instances de la classe Thread. Autrement dit, pour créer et exécuter 10 threads, vous aurez besoin de 10 objets de cette classe. Écrivons l'exemple le plus simple :
public class MyFirstThread extends Thread {

   @Override
   public void run() {
       System.out.println("I'm Thread! My name is " + getName());
   }
}
Pour créer et lancer des threads, nous devons créer une classe et en hériter du java.lang. Threadet remplacez la méthode qu'il contient run(). Le dernier est très important. C'est dans la méthode que run()nous prescrivons la logique que notre thread doit exécuter. Maintenant, si nous créons une instance MyFirstThreadet l'exécutons, la méthode run()imprimera une ligne avec son nom sur la console : la méthode getName()imprime le nom « système » du thread, qui est attribué automatiquement. Mais en fait, pourquoi « si » ? Créons et testons !
public class Main {

   public static void main(String[] args) {

       for (int i = 0; i < 10; i++) {

           MyFirstThread thread = new MyFirstThread();
           thread.start();
       }
   }
}
Sortie de la console :  Je suis Thread ! Je m'appelle Thread-2, je suis Thread ! Je m'appelle Thread-1, je suis Thread ! Je m'appelle Thread-0, je suis Thread ! Je m'appelle Thread-3, je suis Thread ! Je m'appelle Thread-6, je suis Thread ! Je m'appelle Thread-7, je suis Thread ! Je m'appelle Thread-4, je suis Thread ! Je m'appelle Thread-5, je suis Thread ! Je m'appelle Thread-9, je suis Thread ! Je m'appelle Thread-8. Nous créons 10 threads (objets) MyFirstThreaddont nous héritons Threadet les lançons en appelant la méthode de l'objet start(). Après avoir appelé une méthode , start()sa méthode commence à fonctionner run()et la logique qui y est écrite est exécutée. Attention : les noms des sujets ne sont pas dans l'ordre. C'est assez étrange, pourquoi n'ont-ils pas été exécutés à tour de rôle : Thread-0, Thread-1, Thread-2et ainsi de suite ? C’est exactement un exemple de cas où la pensée standard « séquentielle » ne fonctionnera pas. Le fait est que dans ce cas, nous émettons uniquement des commandes pour créer et lancer 10 threads. L'ordre dans lequel ils doivent être lancés est décidé par le planificateur de threads : un mécanisme spécial à l'intérieur du système d'exploitation. La manière exacte dont il est structuré et selon quels principes il prend ses décisions est un sujet très complexe, et nous n'y reviendrons pas maintenant. La principale chose à retenir est que le programmeur ne peut pas contrôler la séquence d’exécution des threads. Pour prendre conscience de la gravité de la situation, essayez d’exécuter la méthode main()de l’exemple ci-dessus plusieurs fois. Sortie de la deuxième console : je suis Thread ! Je m'appelle Thread-0, je suis Thread ! Je m'appelle Thread-4, je suis Thread ! Je m'appelle Thread-3, je suis Thread ! Je m'appelle Thread-2, je suis Thread ! Je m'appelle Thread-1, je suis Thread ! Je m'appelle Thread-5, je suis Thread ! Je m'appelle Thread-6, je suis Thread ! Je m'appelle Thread-8, je suis Thread ! Je m'appelle Thread-9, je suis Thread ! Je m'appelle Thread-7 Troisième sortie de la console : je suis Thread ! Je m'appelle Thread-0, je suis Thread ! Je m'appelle Thread-3, je suis Thread ! Je m'appelle Thread-1, je suis Thread ! Je m'appelle Thread-2, je suis Thread ! Je m'appelle Thread-6, je suis Thread ! Je m'appelle Thread-4, je suis Thread ! Je m'appelle Thread-9, je suis Thread ! Je m'appelle Thread-5, je suis Thread ! Je m'appelle Thread-7, je suis Thread ! Je m'appelle Thread-8

Problèmes créés par le multithreading

Dans l'exemple des livres, vous avez vu que le multithreading résout des problèmes assez importants et que son utilisation accélère le travail de nos programmes. Dans de nombreux cas – plusieurs fois. Mais ce n’est pas pour rien que le multithreading est considéré comme un sujet complexe. Après tout, si elle est mal utilisée, elle crée des problèmes au lieu de les résoudre. Quand je dis « créer des problèmes », je ne parle pas de quelque chose d’abstrait. Le multithreading peut causer deux problèmes spécifiques : les blocages et les conditions de concurrence critique. Un blocage est une situation dans laquelle plusieurs threads attendent des ressources occupées les uns par les autres, et aucun d'entre eux ne peut continuer à s'exécuter. Nous en parlerons davantage dans les prochaines conférences, mais pour l'instant cet exemple suffira : Multithreading en Java : essence, avantages et pièges courants - 4 imaginez que le thread-1 travaille avec un objet-1 et que le thread-2 travaille avec un objet-2. Le programme s'écrit ainsi :
  1. Thread-1 cessera de fonctionner avec l'objet-1 et passera à l'objet-2 dès que Thread-2 cessera de fonctionner avec l'objet 2 et passera à l'objet-1.
  2. Thread-2 cessera de fonctionner avec l'objet-2 et passera à l'objet-1 dès que Thread-1 cessera de fonctionner avec l'objet 1 et passera à l'objet-2.
Même sans connaissance approfondie du multithreading, vous pouvez facilement comprendre que rien n'en sortira. Les fils ne changeront jamais de place et s’attendront pour toujours. L’erreur semble évidente, mais en réalité elle ne l’est pas. Vous pouvez facilement l'autoriser dans le programme. Nous examinerons des exemples de code provoquant un blocage dans les conférences suivantes. À propos, Quora propose un excellent exemple concret expliquant ce qu'est une impasse . « Dans certains États indiens, ils ne vous vendront pas de terres agricoles à moins que vous ne soyez enregistré en tant qu'agriculteur. Cependant, vous ne serez pas enregistré comme agriculteur si vous ne possédez pas de terres agricoles. Super, que dire ! :) Parlons maintenant de la condition de course - l'état de la course. Multithreading en Java : essence, avantages et pièges courants - 5Une condition de concurrence critique est un défaut de conception dans un système ou une application multithread dans lequel le fonctionnement du système ou de l'application dépend de l'ordre dans lequel les parties du code sont exécutées. Rappelez-vous l'exemple avec des threads en cours d'exécution :
public class MyFirstThread extends Thread {

   @Override
   public void run() {
       System.out.println("Выполнен поток " + getName());
   }
}

public class Main {

   public static void main(String[] args) {

       for (int i = 0; i < 10; i++) {

           MyFirstThread thread = new MyFirstThread();
           thread.start();
       }
   }
}
Imaginez maintenant que le programme soit responsable du fonctionnement d'un robot qui prépare la nourriture ! Thread-0 sort les œufs du réfrigérateur. Le flux 1 allume la cuisinière. Stream-2 sort une poêle à frire et la met sur le feu. Le flux 3 allume un feu sur la cuisinière. Le flux 4 verse de l'huile dans la poêle. Le flux 5 casse les œufs et les verse dans la poêle. Le Stream 6 jette les coquilles à la poubelle. Stream-7 retire les œufs brouillés finis du feu. Potok-8 met des œufs brouillés dans une assiette. Stream-9 fait la vaisselle. Regardez les résultats de notre programme : Thread-0 exécuté Thread-2 thread exécuté Thread-1 thread exécuté Thread-4 thread exécuté Thread-9 thread exécuté Thread-5 thread exécuté Thread-8 thread exécuté Thread-7 thread exécuté Thread-7 thread exécuté -3 Thread-6 thread exécuté. Le script est-il amusant ? :) Et tout cela parce que le fonctionnement de notre programme dépend de l'ordre dans lequel les threads sont exécutés. A la moindre violation de la séquence, notre cuisine se transforme en enfer, et un robot devenu fou détruit tout autour d'elle. C'est également un problème courant dans la programmation multithread, dont vous entendrez parler plus d'une fois. À la fin de la conférence, je voudrais vous recommander un livre sur le multithreading.
Multithreading en Java : essence, avantages et pièges courants - 6
« Java Concurrency in Practice » a été écrit en 2006, mais n'a pas perdu de sa pertinence. Il couvre la programmation multithread en Java, en commençant par les bases et en terminant par une liste des erreurs et anti-modèles les plus courants. Si jamais vous décidez de devenir un gourou de la programmation multithread, ce livre est une lecture incontournable. Rendez-vous aux prochaines conférences ! :)
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION