Ce tutoriel examine les bases des threads : ce qu'ils sont, pourquoi ils sont utiles et comment commencer à écrire des programmes simples qui utilisent des threads. (Cours recommandé : Tutoriel vidéo Java)
Qu'est-ce qu'un fil de discussion ?
Presque tous les systèmes d'exploitation prennent en charge le concept de processus - des programmes quelque peu isolés les uns des autres et exécutés indépendamment.
Le Threading est un outil qui permet à plusieurs activités de coexister dans un processus. La plupart des systèmes d'exploitation modernes prennent en charge les threads, et le concept de thread existe sous diverses formes depuis de nombreuses années. Java a été le premier langage de programmation majeur à inclure explicitement les threads dans le langage lui-même ; il ne considérait pas les threads comme un outil du système d'exploitation sous-jacent.
Parfois, les threads sont également appelés processus légers. Tout comme les processus, les threads sont des chemins d'exécution indépendants et simultanés dans le programme. Chaque thread possède sa propre pile, son propre compteur de programme et ses propres variables locales. Cependant, les threads au sein d’un processus sont moins isolés les uns des autres que les processus séparés. Ils partagent la mémoire, les descripteurs de fichiers et d’autres états que chaque processus devrait avoir.
Un processus peut prendre en charge plusieurs threads, qui semblent s'exécuter simultanément mais ne sont pas synchronisés les uns avec les autres. Plusieurs threads d'un processus partagent le même espace d'adressage mémoire, ce qui signifie qu'ils peuvent accéder aux mêmes variables et objets, et qu'ils allouent des objets à partir du même tas. Bien que cela facilite le partage d'informations entre les threads, vous devez veiller à ce qu'elles n'interfèrent pas avec d'autres threads du même processus.
Les outils de threading et l'API Java sont d'une simplicité trompeuse. Cependant, écrire des programmes complexes utilisant efficacement les threads n’est pas très simple. Étant donné que plusieurs threads coexistent dans le même espace mémoire et partagent les mêmes variables, vous devez veiller à ce que vos threads n'interfèrent pas les uns avec les autres.
Chaque programme Java utilise des threads
Chaque programme Java a au moins un thread - le thread principal. Lorsqu'un programme Java démarre, la JVM crée le thread principal et appelle la méthode main() du programme dans ce thread.
La JVM crée également d'autres threads que vous ne voyez généralement pas -- par exemple, des threads liés au garbage collection, à la terminaison d'objets et à d'autres tâches de maintenance de la JVM. D'autres outils créent également des threads, tels que AWT (Abstract Windowing Toolkit) ou Swing UI Toolkit, des conteneurs de servlets, des serveurs d'applications et RMI (Remote Method Invocation).
Pourquoi utiliser les fils de discussion ?
Il existe de nombreuses raisons d'utiliser des threads dans les programmes Java. Si vous utilisez la technologie Swing, servlet, RMI ou Enterprise JavaBeans (EJB), vous ne réalisez peut-être pas que vous utilisez déjà des threads.
Certaines raisons d'utiliser les threads sont qu'ils peuvent aider :
Rendre l'interface utilisateur plus réactive
Profiter des systèmes multiprocesseurs
Simplifier la modélisation
Simplifier la modélisation
🎜> Effectuer un traitement asynchrone ou en arrière-plan
Interface utilisateur plus réactive
Les boîtes à outils d'interface utilisateur basées sur les événements (telles que AWT et Swing) ont un fil d'événements qui gère Événements de l’interface utilisateur tels que les frappes au clavier ou les clics de souris. Les programmes AWT et Swing connectent les écouteurs d'événements aux objets de l'interface utilisateur. Ces écouteurs sont avertis lorsqu'un événement spécifique se produit, tel qu'un clic sur un bouton. Les écouteurs d'événements sont appelés dans le fil d'événements AWT. Si l'écouteur d'événement doit effectuer une tâche de longue durée, telle que vérifier l'orthographe dans un document volumineux, le fil d'exécution de l'événement sera occupé à exécuter le correcteur orthographique, donc aucun traitement supplémentaire ne pourra être effectué avant l'écouteur d'événement. est terminé. Cela peut donner l’impression que le programme est bloqué, laissant l’utilisateur confus.Pour éviter une réponse retardée de l'interface utilisateur, l'écouteur d'événements doit placer les tâches plus longues dans un autre thread, afin que le thread AWT puisse continuer à traiter les événements de l'interface utilisateur (y compris l'annulation de l'exécution de la tâche) pendant l'exécution de la tâche. demandes de tâches de longue durée).
Utilisation de systèmes multiprocesseurs
Les systèmes multiprocesseurs (MP) sont plus courants que par le passé. Auparavant, on ne les trouvait que dans les grands centres de données et les installations de calcul scientifique. Aujourd'hui, de nombreux systèmes de serveurs bas de gamme - et même certains systèmes de bureau - disposent de plusieurs processeurs. Les systèmes d'exploitation modernes, notamment Linux, Solaris et Windows NT/2000, peuvent tirer parti de plusieurs processeurs et planifier l'exécution des threads sur n'importe quel processeur disponible.L'unité de base de la planification est généralement un thread ; si un programme n'a qu'un seul thread actif, il ne peut s'exécuter que sur un processeur à la fois. Si un programme a plusieurs threads actifs, plusieurs threads peuvent être planifiés simultanément. Dans un programme bien conçu, l’utilisation de plusieurs threads peut améliorer le débit et les performances du programme.
Modélisation simplifiée
Dans certains cas, l'utilisation de threads peut rendre les programmes plus simples à écrire et à maintenir. Considérez une application de simulation dans laquelle vous souhaitez simuler des interactions entre plusieurs entités. Donner à chaque entité son propre thread peut grandement simplifier de nombreuses applications de simulation et de modélisation.Un autre exemple d'utilisation de threads séparés pour simplifier un programme est lorsqu'une application possède plusieurs composants indépendants pilotés par des événements. Par exemple, une application peut disposer d'un composant qui compte les secondes après un événement et met à jour l'affichage à l'écran. Plutôt que d'avoir une boucle principale qui vérifie périodiquement l'heure et met à jour l'affichage, il est plus simple et moins sujet aux erreurs de laisser un thread ne rien faire et dormir jusqu'à un certain temps plus tard, lorsque le compteur à l'écran est mis à jour. De cette façon, le thread principal n’a pas du tout besoin de se soucier du timer.
Traitement asynchrone ou en arrière-plan
Une application serveur reçoit les entrées d'une source distante telle qu'un socket. Lors de la lecture depuis un socket, si aucune donnée n'est actuellement disponible, l'appel à SocketInputStream.read() se bloquera jusqu'à ce que les données soient disponibles.
Si un programme monothread lit à partir d'un socket et que l'entité à l'autre extrémité du socket n'envoie aucune donnée, alors le programme attendra indéfiniment sans effectuer d'autres traitements. Au lieu de cela, un programme peut interroger le socket pour voir si les données sont disponibles, mais cela n'est généralement pas utilisé en raison de l'impact sur les performances.
Cependant, si vous créez un thread pour lire à partir du socket, alors pendant que ce thread attend l'entrée du socket, le thread principal peut effectuer d'autres tâches. Vous pouvez même créer plusieurs threads afin de pouvoir lire à partir de plusieurs sockets en même temps. De cette façon, vous serez rapidement averti lorsque des données sont disponibles (lorsque le thread en attente est réveillé) sans avoir à interroger fréquemment pour vérifier si les données sont disponibles. Le code qui utilise des threads pour attendre sur les sockets est également plus simple et moins sujet aux erreurs que l'interrogation.
Simple, mais parfois risqué
Bien que les outils de threading Java soient très faciles à utiliser, il existe certains risques que vous devriez essayer d'éviter lorsque vous créez des programmes multithreads .
Lorsque plusieurs threads accèdent au même élément de données (comme un champ statique, un champ d'instance d'un objet accessible globalement ou une collection partagée), vous devez vous assurer qu'ils coordonnent leur accès aux données afin que ils peuvent tous voir les données des vues cohérentes sans interférer avec les modifications des uns et des autres. Pour atteindre cet objectif, le langage Java fournit deux mots-clés : synchronisé et volatile. Nous examinerons le but et la signification de ces mots-clés plus loin dans ce didacticiel.
Lorsque vous accédez à une variable à partir de plusieurs threads, vous devez vous assurer que l'accès est correctement synchronisé. Pour des variables simples, déclarer la variable volatile peut suffire, mais dans la plupart des cas, une synchronisation est requise.
Si vous comptez utiliser la synchronisation pour protéger l'accès à une variable partagée, vous devez vous assurer que la synchronisation est utilisée partout dans le programme où l'on accède à la variable.
N'en faites pas trop
Bien que les threads puissent grandement simplifier de nombreux types d'applications, la surutilisation des threads peut compromettre les performances d'un programme et sa maintenabilité. Le thread a consommé des ressources. Par conséquent, il existe une limite au nombre de threads pouvant être créés sans dégrader les performances.
Surtout sur un système monoprocesseur, l'utilisation de plusieurs threads ne permettra pas d'exécuter plus rapidement les programmes qui consomment principalement des ressources CPU.
Exemple : utilisez un thread pour le timing et un autre thread pour effectuer le travail
L'exemple suivant utilise deux threads, un pour le timing et un pour l'exécution du travail réel. Le fil principal utilise un algorithme très simple pour calculer les nombres premiers.
Avant de démarrer, il crée et démarre un thread de minuterie qui dort pendant dix secondes, puis définit un indicateur que le thread principal vérifie. Après dix secondes, le thread principal s'arrêtera. Notez que l'indicateur partagé est déclaré volatile.
/** * CalculatePrimes -- calculate as many primes as we can in ten seconds */ public class CalculatePrimes extends Thread { public static final int MAX_PRIMES = 1000000; public static final int TEN_SECONDS = 10000; public volatile boolean finished = false; public void run() { int[] primes = new int[MAX_PRIMES]; int count = 0; for (int i=2; count<MAX_PRIMES; i++) { // Check to see if the timer has expired if (finished) { break; } boolean prime = true; for (int j=0; j<count; j++) { if (i % primes[j] == 0) { prime = false; break; } } if (prime) { primes[count++] = i; System.out.println("Found prime: " + i); } } } public static void main(String[] args) { CalculatePrimes calculator = new CalculatePrimes(); calculator.start(); try { Thread.sleep(TEN_SECONDS); } catch (InterruptedException e) { // fall through } calculator.finished = true; } }
Résumé
Le langage Java comprend de puissantes fonctionnalités de threading intégrées au langage. Vous pouvez utiliser des outils de threading pour :
Augmenter la réactivité des applications GUI
Tirer parti des systèmes multiprocesseurs
Simplifier la logique du programme lorsque le programme comporte plusieurs entités indépendantes
Effectuer le blocage des E/S sans bloquer l'ensemble du programme
Lors de l'utilisation de plusieurs threads, il faut veiller à suivre les règles de partage de données entre les threads, dont nous parlerons dans Partage. Ces règles sont abordées dans Accès aux données. Toutes ces règles se résument à un principe de base : ne pas oublier de se synchroniser.
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!