Table des matières(?)[-]
On étend la classe Javalangthread Two implémente l'interface javalangrunnable trois différences entre le thread et le fil de filetage à quatre file
Cet article parle principalement des méthodes d'utilisation du multi-threading en Java, de la synchronisation du thread, du transfert de données de thread, de l'état du thread et de l'utilisation et de la vue d'ensemble de la fonction de thread correspondant.
Tout d'abord, parlons de la différence entre un processus et un fil:
Processus: Chaque processus a un code indépendant et un espace de données (contexte de processus), et la commutation entre les processus aura une surcharge importante. Un processus contient des threads 1-N.
Thread: Le même type de threads partage le code et l'espace de données. Chaque thread a une pile d'exécution indépendante et un compteur de programme (PC), et la surcharge de commutation de thread est petite.
Comme un processus, les threads sont divisés en cinq étapes: création, prête, en cours d'exécution, bloquant et se terminant.
Le multi-processus signifie que le système d'exploitation peut exécuter plusieurs tâches (programmes) en même temps.
Multithreading fait référence à plusieurs flux séquentiels exécutés dans le même programme.
En Java, il existe deux façons d'implémenter le multi-threading. L'un consiste à poursuivre la classe de threads, et l'autre est d'implémenter l'interface gérée.
1. Étendre la classe java.lang.thread
package com.multithread.learning; / ** * @ Functon Multithreading Learning * @ Author lin bingwen * @ time 2015.3.9 * / class Thread1 étend Thread {Private String Name; public thread1 (String name) {this.name = name; } public void run () {for (int i = 0; i <5; i ++) {System.out.println (name + "run:" + i); essayez {sleep ((int) math.random () * 10); } catch (InterruptedException e) {e.printStackTrace (); }}}} public class main {public static void main (String [] args) {Thread1 mth1 = new Thread1 ("a"); Thread1 mth2 = nouveau thread1 ("b"); Mth1.start (); MTH2.Start (); }} Sortir:
Une course: 0
B Run: 0
Une course: 1
Une course: 2
Une course: 3
Une course: 4
B Run: 1
B Run: 2
B Run: 3
B Run: 4
Exécutez-le à nouveau:
Une course: 0
B Run: 0
B Run: 1
B Run: 2
B Run: 3
B Run: 4
Une course: 1
Une course: 2
Une course: 3
Une course: 4
illustrer:
Lorsque le programme démarre et s'exécute principale, la machine virtuelle Java démarre un processus et le principal thread principal est créé lorsque le principal () est appelé. Avec la méthode de démarrage des deux objets de MiTiTay, les deux autres threads sont également démarrés, de sorte que l'application entière s'exécute sous plusieurs threads.
Remarque: La méthode start () est appelée à ne pas exécuter immédiatement le code multithread, mais fait plutôt le thread de devenir un état exécutable. Lorsqu'il s'exécute est déterminé par le système d'exploitation.
D'après les résultats du programme en cours, nous pouvons constater que les programmes multi-thread sont exécutés de l'ordre. Par conséquent, seul le code exécuté hors service doit être conçu comme multithread.
Le but de l'invocation de la méthode thread.sleep () est d'empêcher le thread actuel d'occuper les ressources CPU obtenues par le processus seul, afin de laisser un certain temps à exécuter d'autres threads.
En fait, l'ordre d'exécution de tous les code multi-thread est incertain et les résultats de chaque exécution sont aléatoires.
Cependant, si la méthode de démarrage est appelée à plusieurs reprises, un java.lang.LelegalThreadStateException se produira.
Thread1 mth1 = nouveau thread1 ("a"); Thread1 MTH2 = MTH1; Mth1.start (); MTH2.Start (); Sortir:
Exception dans Thread "Main" Java.lang.LelegalThreadStateException
sur java.lang.thread.start (source inconnue)
sur com.multithread.learning.main.main (main.java:31)
Une course: 0
Une course: 1
Une course: 2
Une course: 3
Une course: 4
2. Implémentez l'interface java.lang.runnable
/ ** * @ Functon Multithreading Learning * @ Author lin bingwen * @ time 2015.3.9 * / package com.multithread.runnable; class Thread2 implémente Runnable {private String Name; public thread2 (String name) {this.name = name; } @Override public void run () {for (int i = 0; i <5; i ++) {System.out.println (name + "run:" + i); essayez {thread.sleep ((int) math.random () * 10); } catch (InterruptedException e) {e.printStackTrace (); }}}} public class main {public static void main (String [] args) {new Thread (new Thread2 ("C")). start (); nouveau thread (nouveau thread2 ("d")). start (); }} Sortir:
C Run: 0
D Run: 0
D Run: 1
C Run: 1
D Run: 2
C Run: 2
D Run: 3
C Run: 3
D Run: 4
C Run: 4
illustrer:
La classe Thread2 implémente l'interface runnable, ce qui fait que la classe a les caractéristiques d'une classe multithread. La méthode run () est une convention pour les programmes multithreads. Tout le code multithread est dans la méthode d'exécution. La classe de threads est en fait une classe qui implémente l'interface Runnable.
Lors du démarrage du multi-threading, vous devez d'abord construire l'objet via le thread de constructeur de filetage (Target Runnable), puis appeler la méthode start () de l'objet thread pour exécuter le code multi-thread.
En fait, tout le code multithread est exécuté en exécutant la méthode start () de Thread. Par conséquent, qu'il s'agisse d'étendre la classe de thread ou d'implémenter l'interface Runnable pour implémenter le multi-threading, ou finalement de contrôler les threads via l'API de l'objet Thread, de vous familiariser avec l'API de classe de threads est la base de la programmation multi-thread.
3. La différence entre le thread et le runnable
Si une classe hérite du thread, il ne convient pas au partage des ressources. Cependant, si l'interface gérée est implémentée, il est facile d'implémenter le partage des ressources.
package com.multithread.learning; / ** * @ Functon Multi-thread Learning, Hériter Thread, les ressources ne peuvent pas être partagées * @ auteur lin bingwen * @ time 2015.3.9 * / class Thread1 étend Thread {private int count = 5; nom de chaîne privé; public thread1 (String name) {this.name = name; } public void run () {for (int i = 0; i <5; i ++) {System.out.println (name + "run count =" + count--); essayez {sleep ((int) math.random () * 10); } catch (InterruptedException e) {e.printStackTrace (); }}}} public class main {public static void main (String [] args) {Thread1 mth1 = new Thread1 ("a"); Thread1 mth2 = nouveau thread1 ("b"); Mth1.start (); MTH2.Start (); }} Sortir:
B Count de course = 5
Un compte de course = 5
B Count de course = 4
B Count de course = 3
B Count de course = 2
B Count de course = 1
Un compte de course = 4
Un compte de course = 3
Un compte de course = 2
Un compte de course = 1
D'après ce qui précède, nous pouvons voir que les dénombrements sont différents entre les différents threads, qui auront un gros problème pour le système de vente de billets. Bien sûr, la synchronisation peut être utilisée ici. Utilisons Runnable pour le faire ici
/ ** * @ Functon Multi-Threading Learning hérite Runnable, les ressources peuvent être partagées * @ auteur lin bingwen * @ time 2015.3.9 * / package com.multithread.runnable; class Thread2 implémente Runnable {private int count = 15; @Override public void run () {for (int i = 0; i <5; i ++) {System.out.println (thread.currentThread (). GetName () + "run count =" + count--); essayez {thread.sleep ((int) math.random () * 10); } catch (InterruptedException e) {e.printStackTrace (); }}}}} classe publique Main {public static void main (String [] args) {Thread2 my = new Thread2 (); Nouveau thread (mon, "C"). start (); // le même MT, mais ce n'est pas possible dans le fil. Si vous instanciez l'objet MT, une exception apparaîtra d'un nouveau thread (mon, "d"). Start (); nouveau fil (mon, "e"). start (); }} Sortir:
C Compte de course = 15
D Count de course = 14
E Count Count = 13
D count compter = 12
D Count de course = 10
D count compter = 9
D count compter = 8
C Compte de course = 11
E Count Count = 12
C Compte de course = 7
E count compter = 6
C Compte de course = 5
E count compter = 4
C Count de course = 3
E count compter = 2
Ici, nous devons noter que chaque thread utilise le même objet d'instanciation. Si ce n'est pas la même chose, l'effet sera le même que ci-dessus!
Résumer:
Les avantages de l'implémentation de l'interface runnable sur l'héritage de la classe de threads:
1): Convient pour plusieurs threads avec le même code de programme pour traiter la même ressource
2): peut éviter la limitation de l'héritage unique en Java
3): augmenter la robustesse du programme, le code peut être partagé par plusieurs threads, et le code et les données sont indépendants
Permettez-moi de vous rappeler: la méthode principale est en fait un fil. En Java, les fils sont lancés en même temps. Quant au moment et à lequel est exécuté en premier, cela dépend entièrement de qui obtient d'abord les ressources du CPU.
En Java, au moins 2 threads sont lancés chaque fois que le programme s'exécute. L'un est le fil principal et l'autre est le fil de collecte des ordures. Parce que chaque fois qu'une classe est exécutée à l'aide de commandes Java, un JVM sera réellement démarré et chaque stage JVM démarre un processus dans le système d'exploitation.
4. Transition de l'état du thread
1. Nouveau état (nouveau): un nouvel objet de thread est créé.
2. État prêt (Runnable): Une fois l'objet de thread créé, d'autres threads appellent la méthode start () de l'objet. Le fil de cet état est situé dans le pool de threads runnable et devient coulable, en attendant d'obtenir les droits d'utilisation du CPU.
3. État d'exécution: le thread de l'état prêt acquiert le CPU et exécute le code du programme.
4. État bloqué: l'état bloqué signifie que le fil abandonne les droits d'utilisation du CPU pour une raison quelconque et cesse temporairement de fonctionner. Ce n'est que lorsque le fil entre dans l'état prêt qu'il a une chance d'aller à l'état de course. Il existe trois types de blocage:
(1) En attente de blocage: le thread en cours d'exécution exécute la méthode Wait () et le JVM mettra le fil dans la piscine d'attente.
(2) Blocage synchrone: Lorsque le thread en cours d'exécution acquiert le verrou de synchronisation de l'objet, si le verrou de synchronisation est occupé par d'autres threads, le JVM mettra le fil dans le pool de verrouillage.
(Iii), autre blocage: Lorsqu'un thread en cours d'exécution exécute la méthode sleep () ou join (), ou émet une demande d'E / S, le JVM définira le thread à un état de blocage. Lorsque l'état de sommeil () a chronométré, join () a attendu que le fil se termine ou l'expression, ou le traitement d'E / S a été terminé, le fil est rentré à l'état prêt.
5. État mort: le thread a fini d'exécuter ou de quitter la méthode run () en raison d'une exception, et le fil met fin à son cycle de vie.
5. Planification des fils
Planification de threads
1. Ajuster la priorité du thread: les threads Java ont une priorité, et les threads avec une grande priorité obtiendront plus d'occasions de s'exécuter.
La priorité des threads Java est représentée par des entiers, avec une plage de valeur de 1 ~ 10. La classe de thread a les trois constantes statiques suivantes:
statique int max_priority
La priorité la plus élevée qu'un fil peut avoir est de 10.
statique int min_priority
La priorité la plus basse qu'un fil peut avoir est 1.
static int norm_priority
La priorité par défaut attribuée au thread est 5.
Les méthodes setPriority () et getPriority () de la classe de thread sont utilisées pour définir et obtenir la priorité du fil respectivement.
Chaque thread a une priorité par défaut. La priorité par défaut du thread principal est thread.norm_priority.
La priorité des fils est héritée. Par exemple, si le thread B est créé dans le thread A, alors B aura la même priorité que A.
Le JVM fournit 10 priorités de thread, mais elle ne mappe pas bien avec les systèmes d'exploitation communs. Si vous souhaitez que le programme soit transféré dans chaque système d'exploitation, vous devez uniquement utiliser la classe de threads avec les trois constantes statiques suivantes comme priorité, ce qui peut garantir que la même priorité adopte la même méthode de planification.
2. Méthode de Sleep: Thread.Sleep (Long Millis) pour faire passer le fil à un état de blocage. Le paramètre Millis définit le temps de sommeil en millisecondes. Lorsque le sommeil est terminé, il devient coulable. La plate-forme Sleep () a une bonne portabilité.
3. Thread Wait: La méthode Wait () dans la classe d'objets fait attendre le thread actuel que d'autres threads appellent la méthode Notify () de l'objet ou la méthode de réveil Notifyall (). Ces deux méthodes de réveil sont également des méthodes dans la classe d'objets, et leur comportement équivaut à appeler Wait (0).
4. Concessions de threads: Thread.yield () Méthode suspend l'objet de thread exécutif et donne l'opportunité d'exécution aux threads avec la même priorité ou supérieure.
5. Méthode du thread: join (), en attendant que d'autres threads se terminent. Appelant la méthode join () d'un autre thread dans le thread actuel, le thread actuel passe à un état de blocage jusqu'à ce que l'autre processus s'exécute et que le thread actuel passe du blocage à l'état prêt.
6. Wirel Wake-up: La méthode Notify () dans la classe d'objets réveille un seul thread en attente sur ce moniteur d'objet. Si tous les threads attendent cet objet, l'un des threads sera sélectionné. Le choix est arbitraire et se produit lors de la prise de décision sur la mise en œuvre. Le thread attend le moniteur de l'objet en appelant l'une des méthodes d'attente. Le thread Waken ne peut pas être exécuté tant que le thread actuel abandonne le verrou sur cet objet. Le thread Waken rivalisera avec tous les autres threads qui sont activement synchronisés sur l'objet d'une manière conventionnelle; Par exemple, le fil Waken n'a pas de privilèges ou d'inconvénients fiables pour être le prochain thread qui verrouille cet objet. Une méthode similaire a également une notifyall () qui réveille tous les threads en attente de ce moniteur d'objet.
Remarque: Les deux méthodes suspendues () et repses () dans le thread ont été abolies dans JDK1.5 et ne seront plus introduites. Parce qu'il y a une tendance à l'impasse.
6. Description des fonctions communes
① Sleep (Long Millis): Laissez le thread en cours d'exécution en cours d'exécution dans le nombre spécifié de millisecondes (suspendre l'exécution)
②Join (): fait référence à l'attente du thread T se terminer.
Comment l'utiliser.
Join est une méthode de la classe de threads. Il est appelé directement après le démarrage du fil. Autrement dit, la fonction de join () est: "attendez que le fil se termine". Ce qui doit être compris ici, c'est que le fil fait référence au fil principal en attendant que le fil d'enfant se termine. C'est-à-dire que le code après le thread enfant appelle la méthode join (), et il ne peut être exécuté que jusqu'à la fin du thread enfant.
Thread t = new ATHREAD (); t.start (); t.join ();
Pourquoi utiliser la méthode join ()
Dans de nombreux cas, le thread principal génère et démarre le thread de l'enfant. Si un grand nombre d'opérations longues sont nécessaires dans le thread de l'enfant, le thread principal se termine souvent avant le fil de l'enfant. Cependant, si le thread principal doit utiliser le résultat de traitement du thread de l'enfant après le traitement d'autres transactions, c'est-à-dire que le thread principal doit attendre que le thread de l'enfant termine l'exécution avant la fin. À l'heure actuelle, la méthode join () doit être utilisée.
Pas de jointure. / ** * @ Functon Multithreading Learning, join * @ auteur lin bingwen * @ time 2015.3.9 * / package com.multithread.join; class thread1 étend Thread {private String Name; public thread1 (String name) {super (name); this.name = name; } public void run () {System.out.println (thread.currentThread (). getName () + "Thread Start!"); pour (int i = 0; i <5; i ++) {System.out.println ("Subthread" + name + "run:" + i); essayez {sleep ((int) math.random () * 10); } catch (InterruptedException e) {e.printStackTrace (); }} System.out.println (thread.currentThread (). GetName () + "Thread Run se termine!"); }} public class main {public static void main (String [] args) {System.out.println (thread.currentThread (). getName () + "Main Thread Run start!"); Thread1 mth1 = nouveau thread1 ("a"); Thread1 mth2 = nouveau thread1 ("b"); Mth1.start (); MTH2.Start (); System.out.println (thread.currentThread (). GetName () + "Le thread principal des termes de filetage!"); }} Résultat de sortie:
Le fil principal principal commence à fonctionner!
Le fil principal principal se termine en cours d'exécution!
B Le fil de fil commence!
Child Thread B fonctionne: 0
Une course de fil commence!
Enfant Thread A Runs: 0
Child Thread B fonctionne: 1
Enfant Fil A Runs: 1
Enfant Fil A Runs: 2
Child File A Runs: 3
Enfant Fil A Runs: 4
Un fil de fil se termine!
Child Thread B fonctionne: 2
Child Thread B fonctionne: 3
Child Thread B fonctionne: 4
B Le fil s'étend!
Ont constaté que le fil principal s'est terminé plus tôt que le fil de l'enfant
Rejoindre
classe publique Main {public static void main (String [] args) {System.out.println (thread.currentThread (). getName () + "Main Thread Run start!"); Thread1 mth1 = nouveau thread1 ("a"); Thread1 mth2 = nouveau thread1 ("b"); Mth1.start (); MTH2.Start (); essayez {mth1.join (); } catch (InterruptedException e) {e.printStackTrace (); } essayez {mth2.join (); } catch (InterruptedException e) {e.printStackTrace (); } System.out.println (thread.currentThread (). GetName () + "Le fil principal se termine!"); }} Résultats en cours:
Le fil principal principal commence à fonctionner!
Une course de fil commence!
Enfant Thread A Runs: 0
B Le fil de fil commence!
Child Thread B fonctionne: 0
Enfant Fil A Runs: 1
Child Thread B fonctionne: 1
Enfant Fil A Runs: 2
Child Thread B fonctionne: 2
Child File A Runs: 3
Child Thread B fonctionne: 3
Enfant Fil A Runs: 4
Child Thread B fonctionne: 4
Un fil de fil se termine!
Le fil principal attendra certainement que les fils d'enfants soient terminés avant sa fin.
③yield (): pause l'objet de thread en cours d'exécution et exécute d'autres threads.
La fonction de la méthode thread.yield () est: pause l'objet de thread exécutant actuellement et exécuter d'autres threads.
Ce que le rendement () devrait faire, c'est de récupérer le thread en cours d'exécution actuel à l'état Runnable pour permettre à d'autres threads avec la même priorité pour obtenir une opportunité d'exécution. Par conséquent, le but d'utiliser le rendement () est de permettre aux threads de la même priorité de fonctionner de manière appropriée. Cependant, en réalité, le rendement () ne peut être garanti pour atteindre l'objectif de la concession, car le thread de concession peut être sélectionné à nouveau par le planificateur de threads.
Conclusion: le rendement () ne fait jamais d'aller au fil à l'état d'attente / sommeil / blocage. Dans la plupart des cas, le rendement () fera passer le fil du fil de l'exécution de l'état, mais peut ne pas fonctionner. Vous pouvez voir l'image ci-dessus.
/ ** * @ Functon Multithreading Learning Rende * @ auteur lin bingwen * @ time 2015.3.9 * / package com.multithread.yield; class Threrafield étend Thread {public threadield (name de chaîne) {super (name); } @Override public void run () {for (int i = 1; i <= 50; i ++) {System.out.println ("" + this.getName () + "-----" + i); // Quand j'ai 30 ans, le fil abandonnera le temps du processeur et laissera les autres ou ses propres threads exécuter (c'est-à-dire celui qui l'attrape d'abord l'exécute) si (i == 30) {this.yield (); }}}} public class main {public static void main (String [] args) {threrayield yt1 = new threrakeield ("zhang san"); Threrayield yt2 = new threrakeield ("li si"); yt1.start (); yt2.start (); }} Résultats en cours:
Le premier cas: Li si (thread) obtiendra le temps de CPU lorsqu'il sera exécuté à 30. À l'heure actuelle, Zhang San (Thread) saisit le temps du CPU et l'exécute.
La deuxième situation: lorsque Li Si (Thread) exécute à 30, le temps du CPU sera abandonné. À ce moment, Li Si (Thread) saisit le temps du CPU et l'exécute.
La différence entre le sommeil () et le rendement ()
La différence entre sleep () et rendement ()): sleep () amène le thread actuel à entrer dans un état stagnant, de sorte que le thread exécutant Sleep () ne sera certainement pas exécuté dans le délai spécifié; rendement () fait simplement remettre le thread actuel à l'état exécutable, de sorte que le thread exécutant rendement () peut être exécuté immédiatement après la saisie de l'état exécutable.
La méthode de sommeil fait dormir le fil en cours d'exécution pendant une période de temps et pénètre dans un état inactif. La durée de cette période est définie par le programme. La méthode de rendement permet au thread actuel de renoncer à la propriété du processeur, mais le temps du transfert est instinctif. En fait, la méthode Rende () correspond à l'opération suivante: Vérifiez d'abord s'il existe des threads avec la même priorité actuellement dans le même état de course. Dans l'affirmative, remettez la propriété du processeur sur ce fil, sinon, continuez à exécuter le fil d'origine. Ainsi, la méthode de rendement () est appelée "concession", qui donne l'opportunité de course à d'autres threads avec la même priorité
De plus, la méthode de sommeil permet aux threads prioritaires plus faibles d'obtenir des opportunités d'exécution, mais lorsque la méthode de rendement () est exécutée, le thread actuel est toujours à l'état exécutable, il est donc impossible de renoncer à des threads de priorité inférieurs pour obtenir la propriété du processeur plus tard. Dans un système en cours d'exécution, si le thread de priorité plus élevé n'appelle pas la méthode de sommeil et n'est pas bloqué par les E / S, le thread de priorité inférieur ne peut qu'attendre que tous les threads de priorité plus élevés pour fonctionner pour fonctionner.
④setPriority (): modifiez la priorité du fil.
Min_priority = 1
Norm_priority = 5
Max_priority = 10
usage:
Thread4 t1 = new Thread4 ("t1");
Thread4 t2 = nouveau thread4 ("t2");
t1.setPriority (thread.max_priority);
t2.SetPriority (thread.min_priority);
⑤interrupt (): interrompre un thread. Cette méthode de fin est plutôt rugueuse. Si le thread T ouvre une ressource et n'a pas eu le temps de le fermer, c'est-à-dire que la méthode d'exécution est forcée de mettre fin au thread avant son exécution, ce qui entraînera la fermeture de la ressource.
La meilleure façon de mettre fin au processus est d'utiliser l'exemple du programme de la fonction Sleep (). Une variable booléenne est utilisée dans la classe de threads pour contrôler lorsque la méthode Run () se termine. Une fois la méthode run () se termine, le fil se termine.
⑥Wait ()
Obj.wait () et obj.notify () doivent être utilisés avec synchronisé (obj), c'est-à-dire attendre et notifier opérer sur le verrou OBJ qui a été acquis. Du point de vue synchronisé, il est obj.wait (), et obj.notify doit être dans le bloc de déclaration synchronisé (obj) {...}. Dans une perspective fonctionnelle, l'attente signifie qu'après que le thread a acquis le verrouillage de l'objet, il libère activement le verrouillage de l'objet et que le fil dormait. Le verrouillage de l'objet ne peut pas être obtenu et que l'exécution se poursuivra jusqu'à ce qu'un autre thread appelle l'objet notify () pour réveiller le thread. La notification correspondante () est l'opération de réveil du verrouillage de l'objet. Mais une chose à noter est qu'après l'appel Notify (), le verrouillage de l'objet n'est pas publié immédiatement, mais l'exécution du bloc de déclaration synchronisé () {} correspondant est terminée et le verrouillage est automatiquement libéré, le JVM sélectionnera au hasard un fil dans le fil de verrouillage d'objet Wait (), attribuez-le à l'objet, réveillez le fil au hasard et continuera à exécuter. Cela fournit des opérations de synchronisation et de réveil entre les threads. Thread.Sleep () et Object.Wait () peuvent suspendre le thread actuel et libérer le contrôle du processeur. La principale différence est que même si Object.Wait () libère le CPU, il libère le contrôle du verrouillage de l'objet.
Il ne suffit pas de comprendre conceptuellement, et il doit être testé dans des exemples pratiques pour mieux comprendre. L'exemple le plus classique de l'application de Object.Wait () et Object.Notify () devrait être le problème de l'impression ABC avec trois threads. Il s'agit d'une question d'interview relativement classique, et les questions sont les suivantes:
Établir trois threads, filetage A imprime 10 fois, le fil B imprime B 10 fois, le fil C imprime C 10 fois, le thread C nécessite du thread pour fonctionner en même temps et ABC est imprimé alternativement 10 fois. Ce problème peut être facilement résolu en utilisant l'objet Wait () et Notify (). Le code est le suivant:
/ ** * attendez l'utilisation * @author dreamsa * @time 2015.3.9 * / package com.multithread.wait; public class myThreadpriter2 implémente Runnable {name de chaîne privée; objet privé Prev; objet privé auto; Private myThreadPrinter2 (String Name, objet prev, objet self) {this.name = name; this.prev = prev; this.self = self; } @Override public void run () {int count = 10; while (count> 0) {synchronisé (prev) {synchronisé (self) {System.out.print (name); compter--; self.notify (); } essayez {prev.Wait (); } catch (InterruptedException e) {e.printStackTrace (); }}}} public static void main (String [] args) lève l'exception {objet a = new object (); Objet b = nouveau objet (); Objet c = nouvel objet (); MyThreadpriter2 pa = new myThreadpriter2 ("a", c, a); MyThreadPrinter2 PB = new MyThreadpriter2 ("B", A, B); MyThreadprinter2 pc = new myThreadpriter2 ("c", b, c); nouveau thread (pa) .start (); Thread.Sleep (100); // Assurez-vous d'exécuter un nouveau thread (PB) .Start (); Thread.Sleep (100); }} Résultat de sortie:
ABCABCABABABABABABABCABABABABABABABABABC
Expliquons d'abord son idée globale. Dans une perspective générale, ce problème est une opération de réveil synchrone entre trois threads. L'objectif principal est d'exécuter trois threads dans Threada-> threadb-> Threadc-> Threada Loop. Afin de contrôler l'ordre de l'exécution du thread, l'ordre de réveil et d'attente doit être déterminé, de sorte que chaque thread doit maintenir deux verrous d'objet en même temps avant de pouvoir continuer l'exécution. Un verrouillage d'objet est Prev, qui est le verrouillage d'objet maintenu par le fil précédent. Un autre est le verrou de l'objet. L'idée principale est que pour contrôler l'ordre d'exécution, vous devez d'abord maintenir le verrouillage de prev, c'est-à-dire que le thread précédent doit libérer son propre verrou d'objet, puis demander son propre verrouillage d'objet. Imprimer lorsque les deux sont les deux. Ensuite, appelez d'abord self.notify () pour libérer son propre verrouillage d'objet, réveillez le thread d'attente suivant, puis appelez prev.AiT () pour libérer le verrouillage de l'objet PREV, terminer le thread actuel et attendre que la boucle soit éveillée à nouveau. Exécutez le code ci-dessus et vous pouvez constater que trois threads impriment ABC dans une boucle, un total de 10 fois. Le processus principal de l'exécution du programme est que le thread A est le premier à s'exécuter, maintient les verrous de l'objet de C et A, puis libère les verrous de A et C, et se réveille B. Le fil B attend le verrouillage A, puis s'applique pour le verrouillage B, puis imprime B, puis libère B, un verrouillage, réveille C, le fil C attend pour le verrou Vous y pensez attentivement, vous constaterez qu'il y a un problème, qui est la condition initiale. Les trois fils sont lancés dans l'ordre de A, B et C. Selon les pensées précédentes, A se réveille B, B réveille C, C puis se réveille A. Cependant, cette hypothèse dépend de l'ordre de planification et d'exécution de threads dans le JVM.
La différence entre l'attente et le sommeil
Points communs:
1. Ils sont tous dans un environnement multi-thread et peuvent bloquer le nombre spécifié de millisecondes lors de l'appel et du retour du programme.
2. À la fois wait () et sleep () peuvent interrompre l'état de pause du thread via la méthode Interrupt (), de sorte que le thread lance immédiatement une interruption.
Si le thread A veut terminer le thread B immédiatement, la méthode d'interruption peut être appelée sur l'instance de thread correspondant au thread B. Si le thread B attend / dorme / joint à ce moment, le thread B lancera immédiatement une interruption, et le renvoie directement dans Catch () {} pour terminer en toute sécurité le thread.
Il convient de noter que l'interruptedException est lancée par le fil lui-même de l'intérieur, et non par la méthode Interrupt (). Lorsque Interrupt () est appelé sur un thread, si le thread exécute du code normal, le thread ne lancera pas du tout une interruption. Cependant, une fois que le thread entre Wait () / Sleep () / Join (), une interruption de l'interruption sera immédiatement lancée.
Différences:
1. Méthodes de classe de threads: Sleep (), rendement (), etc.
Méthodes d'objet: attendre () et notifier (), etc.
2. Chaque objet a une serrure pour contrôler l'accès synchrone. Le mot-clé synchronisé peut interagir avec le verrou de l'objet pour réaliser la synchronisation du thread.
La méthode de sommeil ne libère pas le verrou, tandis que la méthode d'attente libère le verrou, afin que d'autres threads puissent utiliser des blocs ou des méthodes de contrôle synchrones.
3. Attendez, notifiez et notification, ne peut être utilisé que dans les méthodes de contrôle de synchronisation ou les blocs de contrôle de synchronisation, tandis que le sommeil peut être utilisé n'importe où.
4. Le sommeil doit prendre des exceptions, tout en attendant, en notifiant et en notification n'a pas besoin de prendre des exceptions, donc la plus grande différence entre les méthodes de sommeil () et d'attente () est:
Lorsque le sommeil () dort, gardez le verrouillage de l'objet et possède toujours la serrure;
Lorsque Wait () dort, le verrouillage de l'objet est libéré.
Cependant, Wait () et Sleep () peuvent à la fois interrompre l'état de pause du thread via la méthode Interrupt (), de sorte que le thread lance immédiatement une interruption de l'interruption (mais il n'est pas recommandé d'utiliser cette méthode).
Méthode sleep ()
Sleep () fait entrer le thread actuel dans un état stagnant (bloque le thread actuel), abandonnant l'utilisation de la tasse, et le but est d'empêcher le thread actuel d'occuper les ressources CPU obtenues par le processus seul, afin de laisser un certain temps pour que d'autres threads s'exécutent;
Sleep () est une méthode statique de la classe de threads; Par conséquent, il ne peut pas modifier le verrouillage de la machine de l'objet, donc lors de l'appel de la méthode Sleep () dans un bloc synchronisé, bien que le thread soit en sommeil, le verrouillage de la machine de l'objet n'est pas libéré et d'autres threads ne peuvent pas accéder à l'objet (bien qu'il ait toujours le verrouillage de l'objet même lorsqu'il est endormi).
Après l'expiration du temps de sommeil Sleep () Sleep (), le thread ne s'exécute pas nécessairement immédiatement, car d'autres threads peuvent être en cours d'exécution et ne sont pas programmés pour abandonner l'exécution à moins que le thread n'ait une priorité plus élevée.
Méthode Wait ()
La méthode attend () est une méthode dans la classe d'objets; Lorsqu'un thread exécute la méthode Wait (), il entre dans un pool d'attente lié à l'objet et perd en même temps (libère) le verrouillage de la machine de l'objet (perd temporairement le verrouillage de la machine, et le délai d'attente (temps d'attente) devra également retourner le verrouillage de l'objet); D'autres fils peuvent y accéder;
Wait () utilise notifier ou notifier ou un temps de sommeil spécifié pour réveiller le fil dans la piscine d'attente actuelle.
Wiat () doit être placé dans le bloc synchronisé, sinon l'exception "java.lang.illegalmonitorstateException" sera lancée lors de l'exécution du programme.
7. Explication des termes du fil conducteur
Thread principal: le thread généré par le programme d'appel JVM Main ().
Thread actuel: il s'agit d'un concept déroutant. Se réfère généralement à un processus obtenu via thread.currentThread ().
Filant en arrière-plan: fait référence à un fil qui fournit des services à d'autres threads, également connus sous le nom de thread de démon. Le fil de collecte JVM Garbage est un fil d'arrière-plan. La différence entre un thread utilisateur et un thread de démon est de savoir s'il faut attendre que le thread principal mette fin au fil de premier plan en fonction de l'extrémité du thread principal: il fait référence au fil qui accepte le service du thread d'arrière-plan. En fait, les fils d'arrière-plan du premier plan sont connectés ensemble, tout comme la relation entre la marionnette et le manipulateur en coulisses. La marionnette est le fil de premier plan, et le manipulateur des coulisses est le thread d'arrière-plan. Les threads créés par les threads de premier plan sont également des threads de premier plan par défaut. Vous pouvez utiliser des méthodes isdaemon () et setDaemon () pour déterminer et définir si un thread est un thread d'arrière-plan.
Quelques méthodes courantes de classes de threads:
Sleep (): Forcer un fil à dormir en n millisecondes.
Isalive (): détermine si un fil survit.
join (): attendez que le fil se termine.
activeCount (): le nombre de threads actifs dans le programme.
EnuMerate (): Énumérer les threads du programme.
CurrentThread (): obtient le thread actuel.
Isdaemon (): si un fil est un fil de démon.
setDaemon (): Réglez un fil en tant que fil de démon. (La différence entre un thread utilisateur et un thread de démon est de savoir s'il faut attendre que le fil principal se termine en fonction de l'extrémité du thread principal)
setName (): définissez un nom pour le thread.
attendre (): Forcer un fil à attendre.
notify (): notifier un fil pour continuer à fonctionner.
setPriority (): définit la priorité d'un fil.
8. Synchronisation du thread
1. Il existe deux portées de mots clés synchronisés:
1) Il se situe dans une instance d'objet. Amethod () synchronisé {} peut empêcher plusieurs threads d'accéder à la méthode synchronisée de cet objet en même temps (si un objet a plusieurs méthodes synchronisées, tant qu'un thread accède à l'une des méthodes synchronisées, d'autres threads ne peuvent accéder à aucune méthode synchronisée dans l'objet en même temps). À l'heure actuelle, la méthode synchronisée de différentes instances d'objet est ininterrompue. That is to say, other threads can still access the synchronized method in another object instance of the same class at the same time;
2)是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象;
3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;
Java对多线程的支持与同步机制深受大家的喜爱,似乎看起来使用了synchronized关键字就可以轻松地解决多线程共享数据同步问题。到底如何?还得对synchronized关键字的作用进行深入了解才可定论。
总的说来,synchronized关键字可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。如果再细的分类,synchronized可作用于instance变量、object reference(对象引用)、static函数和class literals(类名称字面常量)身上。
在进一步阐述之前,我们需要明确几点:
A.无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,而不是把一段代码或函数当作锁而且同步方法很可能还会被其他线程的对象访问。
B.每个对象只有一个锁(lock)与之相关联。
C.实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无谓的同步控制。
接着来讨论synchronized用到不同地方对代码产生的影响:
假设P1、P2是同一个类的不同对象,这个类中定义了以下几种情况的同步块或同步方法,P1、P2就都可以调用它们。
1. 把synchronized当作函数修饰符时,示例代码如下:
Public synchronized void methodAAA(){//….}这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。
上边的示例代码等同于如下代码:
public void methodAAA(){synchronized (this) // (1){ //…..}}(1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱:(
2.同步块,示例代码如下:
public void method3(SomeObject so) { synchronized(so){ //…..}}这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:
class Foo implements Runnable{ private byte[] lock = new byte[0]; // 特殊的instance变量Public void methodA(){ synchronized(lock) { //… }}//…..}注:零长度的byte数组对象创建起来将比任何对象都经济查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。
3.将synchronized作用于static 函数,示例代码如下:
Class Foo{public synchronized static void methodAAA() // 同步的static 函数{//….}public void methodBBB(){ synchronized(Foo.class) // class literal(类名称字面常量)} }代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。
记得在《Effective Java》一书中看到过将Foo.class和P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。
可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。
1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。
2、线程同步方法是通过锁来实现,每个对象都有切仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程就无法再访问该对象的其他非同步方法。
3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干预。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。
4、对于同步,要时刻清醒在哪个对象上同步,这是关键。
5、编写线程安全的类,需要时刻注意对多个线程竞争访问资源的逻辑和安全做出正确的判断,对“原子”操作做出分析,并保证原子操作期间别的线程无法访问竞争资源。
6、当多个线程等待一个对象锁时,没有获取到锁的线程将发生阻塞。
7、死锁是线程间相互等待锁锁造成的,在实际中发生的概率非常的小。真让你写个死锁程序,不一定好使,呵呵。但是,一旦程序发生死锁,程序将死掉。
九、线程数据传递
在传统的同步开发模式下,当我们调用一个函数时,通过这个函数的参数将数据传入,并通过这个函数的返回值来返回最终的计算结果。但在多线程的异步开发模式下,数据的传递和返回和同步开发模式有很大的区别。由于线程的运行和结束是不可预料的,因此,在传递和返回数据时就无法象函数一样通过函数参数和return语句来返回数据。
9.1、通过构造方法传递数据在创建线程时,必须要建立一个Thread类的或其子类的实例。因此,我们不难想到在调用start方法之前通过线程类的构造方法将数据传入线程。并将传入的数据使用类变量保存起来,以便线程使用(其实就是在run方法中使用)。下面的代码演示了如何通过构造方法来传递数据:
package mythread; public class MyThread1 extends Thread { private String name; public MyThread1(String name) { this.name = name; } public void run() { System.out.println("hello " + name); } public static void main(String[] args) { Thread thread = new MyThread1("world"); thread.start (); } }由于这种方法是在创建线程对象的同时传递数据的,因此,在线程运行之前这些数据就就已经到位了,这样就不会造成数据在线程运行后才传入的现象。如果要传递更复杂的数据,可以使用集合、类等数据结构。使用构造方法来传递数据虽然比较安全,但如果要传递的数据比较多时,就会造成很多不便。由于Java没有默认参数,要想实现类似默认参数的效果,就得使用重载,这样不但使构造方法本身过于复杂,又会使构造方法在数量上大增。因此,要想避免这种情况,就得通过类方法或类变量来传递数据。
9.2、通过变量和方法传递数据
向对象中传入数据一般有两次机会,第一次机会是在建立对象时通过构造方法将数据传入,另外一次机会就是在类中定义一系列的public的方法或变量(也可称之为字段)。然后在建立完对象后,通过对象实例逐个赋值。下面的代码是对MyThread1类的改版,使用了一个setName方法来设置name变量:
package mythread; public class MyThread2 implements Runnable { private String name; public void setName(String name) { this.name = name; } public void run() { System.out.println("hello " + name); } public static void main(String[] args) { MyThread2 myThread = new MyThread2(); myThread.setName("world"); Thread thread = new Thread(myThread); thread.start (); } } 9.3、通过回调函数传递数据
上面讨论的两种向线程中传递数据的方法是最常用的。但这两种方法都是main方法中主动将数据传入线程类的。这对于线程来说,是被动接收这些数据的。然而,在有些应用中需要在线程运行的过程中动态地获取数据,如在下面代码的run方法中产生了3个随机数,然后通过Work类的process方法求这三个随机数的和,并通过Data类的value将结果返回。从这个例子可以看出,在返回value之前,必须要得到三个随机数。也就是说,这个value是无法事先就传入线程类的。
package mythread; class Data { public int value = 0; } class Work { public void process(Data data, Integer numbers) { for (int n : numbers) { data.value += n; } } } public class MyThread3 extends Thread { private Work work; public MyThread3(Work work) { this.work = work; } public void run() { java.util.Random random = new java.util.Random(); Data data = new Data(); int n1 = random.nextInt(1000); int n2 = random.nextInt(2000); int n3 = random.nextInt(3000); work.process(data, n1, n2, n3); // Use the callback function System.out.println(String.valueOf(n1) + "+" + String.valueOf(n2) + "+" + String.valueOf(n3) + "=" + data.value); } public static void main(String[] args) { Thread thread = new MyThread3(new Work()); thread.start (); } }The above is a detailed explanation of Java multi-threading. I hope it can help you learn this part of the knowledge. Merci pour votre soutien pour ce site Web!