Diese Serie basiert auf dem Verlauf der Verfeinerung von Zahlen in Gold, und um besser zu lernen, wurde eine Reihe von Aufzeichnungen gemacht. Dieser Artikel führt hauptsächlich vor. 1. Was ist ein Thread. 2. Grundlegende Operationen von Threads 3. Daemon -Thread 4. Thread Priorität 5. Basis -Thread -Synchronisierungsoperationen
1. Was ist Thread
Themen sind Ausführungseinheiten innerhalb von Prozessen
Es gibt mehrere Threads in einem Prozess.
Ein Thread ist eine Ausführungseinheit innerhalb eines Prozesses.
Der Grund für die Verwendung von Threads ist, dass das Verfahrenswechsel ein sehr schweres Betrieb ist und Ressourcen verbraucht. Wenn Sie mehrere Prozesse verwenden, ist die Anzahl der Parallelität nicht sehr hoch. Themen sind kleinere Zeitplaneinheiten und leichter, sodass Threads im gleichzeitigen Design häufiger eingesetzt werden.
In Java ähnelt das Konzept der Threads dem Konzept von Threads auf Systemebene. Tatsächlich wird JVM Threads in Java auf den Threadbereich des Betriebssystems abbilden.
2. Grundlegende Operationen von Threads
2.1 Fadenstatusdiagramm
Das obige Bild zeigt die grundlegende Operation von Threads in Java.
Wenn ein Thread neu ist, funktioniert der Thread tatsächlich nicht. Es generiert nur eine Entität und der Thread wird tatsächlich gestartet, wenn Sie die Startmethode dieser Instanz aufrufen. Nach dem Start erreicht es den laufbaren Zustand. Runnable bedeutet, dass die Ressourcen des Threads usw. vorbereitet und ausgeführt werden können, aber dies bedeutet nicht, dass er im Ausführungszustand ist. Aufgrund der Drehung von Zeitscheiben kann der Thread zu diesem Zeitpunkt möglicherweise nicht ausgeführt werden. Für uns kann der Thread als ausgeführt angesehen werden, aber ob er ausgeführt wird, hängt jedoch tatsächlich von der Planung der physischen CPU ab. Wenn die Thread -Aufgabe ausgeführt wird, erreicht der Thread den beendeten Zustand.
Manchmal ist es während der Ausführung eines Threads unvermeidlich, dass einige Schlösser oder Objektmonitore angewendet werden. Wenn es nicht abgerufen werden kann, wird der Faden blockiert, gesperrt und erreicht den blockierten Zustand. Wenn dieser Thread die Wartenmethode aufruft, befindet sich er in einem Wartezustand. Der Thread, der in den Wartezustand eintritt, wartet darauf, dass andere Threads ihn benachrichtigen. Nachdem die Benachrichtigung vorgenommen wurde, wechselt der Wartezustand vom Wartezustand in den Laufstaat, um die Ausführung fortzusetzen. Natürlich gibt es zwei Arten von wartenden Zuständen, eine soll auf unbestimmte Zeit warten, bis es benachrichtigt wird. Es wartete auf einen begrenzten Zeitraum. Wenn Sie beispielsweise 10 Sekunden warten oder nicht benachrichtigt wurden, wechselt es automatisch zum Runnable -Status.
2.2 Erstellen Sie einen neuen Thread
Thread = neuer Thread ();
Thread.Start ();
Dies öffnet einen Faden.
Eine Sache zu beachten ist
Thread = neuer Thread ();
thread.run ();
Sie können einen neuen Thread nicht öffnen, indem Sie die Auslaufmethode direkt aufrufen.
Die Startmethode ruft die Auslaufmethode tatsächlich auf einem neuen Betriebssystem -Thread auf. Mit anderen Worten, wenn Sie die Auslaufmethode direkt anstelle der Startmethode aufrufen, wird kein neuer Thread gestartet, sondern Ihre Vorgänge im aktuellen Thread -Aufruf ausführen.
Thread Thread = neuer Thread ("t1") {@Override public void run () {// Todo automatisch generierte Methode stub system.out.println (Thread.CurrentThread (). GetName ()); }}; Thread.Start (); Wenn Start aufgerufen wird, ist der Ausgang T1Thread Thread = neuer Thread ("t1") {@Override public void run () {// Todo automatisch generierte Methode stub system.out.println (Thread.currentThread (). GetName ()); }}; thread.run (); Wenn es ausgeführt wird, wird das Hauptausgang ausgegeben. (Der direkte Turning-Lauf ist eigentlich nur ein gewöhnlicher Funktionsaufruf, und es hat die Rolle des Multi-Threading nicht erreicht.)
Es gibt zwei Möglichkeiten, die Run -Methode zu implementieren
Die erste Methode besteht darin, die Auslaufmethode direkt zu überschreiben. Wie gerade jetzt im Code gezeigt, kann der bequemste Weg mit einer anonymen Klasse erreicht werden.
Thread Thread = neuer Thread ("t1") {@Override public void run () {// Todo automatisch generierte Methode stub system.out.println (Thread.CurrentThread (). GetName ()); }}; Der zweite Weg
Thread T1 = neuer Thread (neuer CreateThead3 ());
CreateThead3 () implementiert die runnable Schnittstelle.
In Zhang Xiaoxiangs Video wird die zweite Methode empfohlen, die besagt, dass sie objektorientierter ist.
2.3 Thread enden
Thread.Stop () wird nicht empfohlen. Es gibt alle Monitore frei
In dem Quellcode wurde deutlich angegeben, dass die Stop -Methode veraltet ist, und der Grund wird auch in Javadoc erklärt.
Der Grund ist, dass die Stop -Methode zu "gewalttätig" ist. Egal wo der Thread ausgeführt wird, es hört sofort auf, den Thread fallen zu lassen.
Wenn der Schreibbefalle die Sperre erhält, beginnt das Schreiben von Daten. Nach dem Schreiben von ID = 1 wird es gestoppt und das Schloss wird freigegeben, wenn Sie auf das festgelegte Name = 1 festlegen. Der Lese -Thread erhält die Sperre für den Lesebetrieb. Die ID -Lesung ist 1 und der Name ist immer noch 0, wodurch Datenkonsistenz verursacht wird.
Das Wichtigste ist, dass diese Art von Irrtum keine Ausnahme auslöst und schwer zu erkennen ist.
2.4 Thread Interrupt
Es gibt 3 Möglichkeiten, den Thread zu unterbrechen
public void thread.interrupt () // Interrupt Thread
öffentlicher boolescher Thread.Interrupted () // Bestimmen Sie, ob er unterbrochen wird
öffentlicher statischer boolescher Thread.Interrupted () // Bestimmen Sie, ob er unterbrochen wird, und löschen Sie den aktuellen Interruptstatus
Was ist Fadenunterbrechung?
Wenn Sie den Interrupt -Mechanismus von Java nicht verstehen, wird eine solche Erklärung sehr wahrscheinlich ein Missverständnis verursachen, und Sie glauben, dass das Aufrufen der Interrupt -Methode des Threads definitiv den Thread unterbrechen wird.
Tatsächlich ist Java -Unterbrechung ein Zusammenarbeitsmechanismus. Mit anderen Worten, das Aufrufen der Interrupt -Methode eines Thread -Objekts unterbricht den laufenden Thread nicht unbedingt, sondern nur den Thread, um sich zum richtigen Zeitpunkt zu unterbrechen. Jeder Thread verfügt über einen booleschen Interrupt -Zustand (nicht unbedingt die Eigenschaft des Objekts, tatsächlich ist dieser Zustand in der Tat kein Threadfeld), und die Interrupt -Methode legt einfach den Zustand auf True fest. Für nicht blockierende Threads wird der Interrupt-Status geändert, dh Thread.Interrupted () wird true zurückkehren und das Programm nicht stoppt.
public void run () {// Thread t1 while (true) {thread.yield (); }} t1.interrupt (); Dies hat keinen Einfluss darauf, den Thread T1 zu unterbrechen, aber das Interrupt -Statusbit wird geändert.
Wenn Sie diesen Thread sehr anmutig beenden möchten, sollten Sie dies tun
public void run () {while (true) {if (thread.currentThread (). isInterrupted ()) {System.out.println ("Interrute!"); brechen; } Thread.yield (); }}Die Verwendung von Interrupts bietet bestimmte Garantien für die Datenkonsistenz.
Für Threads in stornierbaren Blockierungszuständen, wie z. B. Threads, die auf diese Funktionen warten, thread.sleep (), Object.wait () und Thread.Join (). Dieser Thread wirft nach Empfang des Interrupt -Signals eine InterruptedException aus und setzt den Interrupt -Status wieder auf False.
Für Threads in nicht blockierenden Zuständen können Sie einen solchen Code schreiben:
public void run () {while (true) {if (thread.currentThread (). isInterrupted ()) {System.out.println ("Interrute!"); brechen; } try {thread.sleep (2000); } catch (InterruptedException e) {System.out.println ("miteinander im Schlaf"); // Setzen Sie den Interrupt -Status. Das Interrupt -Flag -Bit wird gelöscht, nachdem ein Ausnahme -Thread geworfen wird. } Thread.yield (); }}2.5 Faden hängt
Fäden aussetzen und wieder aufnehmen
suspend () wird das Schloss nicht freigeben
Wenn die Verriegelung vor dem Lebenslauf () auftritt, sind beide Methoden des Deadlocks veraltete Methoden und werden nicht empfohlen.
Der Grund dafür ist, dass Suspend das Schloss erst freigibt, sodass kein Thread auf die von ihm gesperrte kritische Ressource zugreifen kann, bis er von anderen Threads wieder aufgenommen wird. Da die Abfolge der Threads, die ausgeführt werden, nicht gesteuert werden kann, wird die Suspended später immer das Schloss besetzt, wenn die Lebenslaufmethode zuerst ausgeführt wird, wodurch ein Deadlock auftritt.
Verwenden Sie den folgenden Code, um dieses Szenario zu simulieren
Pakettest; public class Test {statisches Objekt u = new Object (); statische testsuspendthread t1 = neue testsuspendthread ("t1"); statische testsuspendthread t2 = neue testsuspendthread ("t2"); public static class testsuspendThread erweitert Thread {public testsuspendThread (String -Name) {setName (name); } @Override public void run () {synchronized (u) {System.out.println ("in" + getName ()); Thread.currentThread (). Suspend (); }} public static void main (String [] args) löst InterruptedException aus {t1.start (); Thread.Sleep (100); t2.Start (); t1.resume (); t2.Resume (); t1.join (); t2.join (); }} Lassen Sie T1 und T2 gleichzeitig um ein Schloss konkurrieren, der Thread, für den er konkurriert wird, wird suspendiert und dann wieder aufgenommen. Logischerweise sollte ein Thread nach dem Wettbewerb um Lebenslauf veröffentlicht werden, und dann konkurriert ein weiterer Thread um das Schloss und die Lebensläufe.
Die Ergebnisausgabe ist:
In T1
in T2
Dies bedeutet, dass beide Threads um das Schloss konkurrieren, aber das rote Licht in der Konsole ist noch eingeschaltet, was bedeutet, dass es Threads geben muss, die in T1 und T2 nicht ausgeführt wurden. Lassen Sie es uns abgeben und einen Blick darauf werfen
Es wurde festgestellt, dass T2 suspendiert wurde. Dies schafft eine Sackgasse.
2.6 Beitritt und YEILD
YEILD ist eine native statische Methode. Diese Methode besteht darin, die CPU -Zeit zu veröffentlichen, die sie besitzt, und dann mit anderen Threads konkurrieren (beachten Sie, dass die Threads von YEILd möglicherweise immer noch um CPU konkurrieren können, und achten Sie auf den Unterschied zum Schlaf). Es wird auch in Javadoc erklärt, dass YEILD eine Methode ist, die im Grunde nicht verwendet wird und im Allgemeinen im Debug und im Test verwendet wird.
Die Join -Methode bedeutet, auf das Ende anderer Threads zu warten, genau wie der Code im Abschnitt "Suspend", möchten Sie, dass der Haupt -Thread auf endet, bevor T1 und T2 endet. Wenn es nicht endet, wird der Hauptfaden dort blockiert.
Pakettest; public class Test {public volatile statische int i = 0; public static class addthread erweitert Thread {@Override public void run () {für (i = 0; i <10000000; i ++); }} public static void main (String [] args) löst InterruptedException aus {addthread at = new addthread (); at.Start (); at.Join (); System.out.println (i); }} Wenn der AT.Join des obigen Codes entfernt wird, wird der Haupt -Thread direkt ausgeführt und der Wert von I wird sehr klein sein. Wenn es einen Join gibt, muss der Wert des gedruckten Ichs 10000000 sein.
Wie wird der Join implementiert?
Die Natur des Join
während (iSalive ())
{
Warten Sie (0);
}
Die join () -Methode kann auch eine Zeit verabschieden, was bedeutet, auf einen begrenzten Zeitraum zu warten und nach dieser Zeit automatisch aufzuwachen.
Es gibt eine solche Frage: Wer wird den Thread benachrichtigen? In der Thread -Klasse gibt es keinen Platz zum Aufrufen von Benachrichtigungen?
In Javadoc wurde eine relevante Erklärung gefunden. Wenn ein Thread abgeschlossen und beendet ist, wird die Benachrichtigung Methode aufgerufen, um alle Threads auf die aktuelle Threadinstanz zu wecken. Diese Operation erfolgt von JVM selbst.
Javadoc gab uns also auch einen Vorschlag, Wartezeit nicht zu verwenden und in Thread -Instanzen zu benachrichtigen/zu benachrichtigen. Da sich JVM selbst nennt, kann es sich von dem erwarteten Ergebnis Ihres Anrufs unterscheiden.
3. Dämon -Thread
Wenn Sie einige systematische Dienste im Hintergrund schweigend abschließen, wie z. B. Müllsammlungen und JIT -Threads, können als Daemon -Threads verstanden werden.
Wenn alle nicht-daemonischen Prozesse in einer Java-Anwendung enden, wird die virtuelle Java-Maschine auf natürliche Weise beendet.
Ich habe einen Artikel über die Implementierung in Python geschrieben. Schauen Sie ihn hier an.
Es ist relativ einfach, in Java ein Daemon zu werden.
Thread t = neuer Daemont ();
T.Setdaemon (wahr);
t.start ();
Dies öffnet einen Daemon -Thread.
Pakettest; public class test {public static class DaemonThread erweitert Thread {@Override public void run () {for (int i = 0; i <10000000; i ++) {System.out.println ("hi"); }}} public static void main (String [] args) löst unterbrochene Ausnahme {DaemonThread dt = new DaemonThread () aus; dt.start (); }} Wenn Thread DT kein Daemon -Thread ist, können wir nach dem Laufen die Konsolenausgabe Hallo sehen
Beim Beitritt vor dem Start
dt.setdaemon (true);
Die Konsole verlässt direkt und hat keine Ausgabe.
4. Thread -Priorität
In der Thread -Klasse gibt es 3 Variablen, die die Thread -Priorität definieren.
public Final static int min_priority = 1; öffentliches endgültiges statisches int norm_priority = 5; öffentliches endgültiges statisches int max_priority = 10; paketest; public class test {public static class High erweitert Thread {static int count = 0; @Override public void run () {while (true) {synchronized (test.class) {count ++; if (count> 10000000) {System.out.println ("High"); brechen; }}}}} öffentliche statische Klasse Low Extends Thread {static int count = 0; @Override public void run () {while (true) {synchronized (test.class) {count ++; if (count> 10000000) {System.out.println ("niedrig"); brechen; }}}}} public static void main (String [] args) löscht InterruptedException {High High = new High () aus; Niedrig niedrig = neu niedrig (); High.setPriority (thread.max_priority); Low.setPriority (Thread.Min_Priority); niedrig.Start (); High.start (); }} Lassen Sie einen Faden mit hoher Priorität und einen Thread mit niedriger Priorität gleichzeitig um ein Schloss konkurrieren und sehen Sie, welches zuerst abgeschlossen wird.
Natürlich muss es nicht unbedingt zuerst mit hoher Priorität abgeschlossen werden. Nachdem ich es mehrmals ausgeführt hatte, stellte ich fest, dass die Wahrscheinlichkeit einer hohen Prioritätsabschluss relativ hoch ist, aber es ist immer noch möglich, sie zuerst mit niedriger Priorität zu vervollständigen.
5. Basis -Thread -Synchronisationsoperation
synchronisiert und Object.wait () obejct.notify ()
Weitere Informationen zu diesem Abschnitt finden Sie in einem Blog, den ich zuvor geschrieben habe.
Die Hauptsache, die zu beachten ist, ist
Es gibt drei Möglichkeiten, synchronisiert zu werden:
Geben Sie gesperrtes Objekt an: Sperren Sie das angegebene Objekt und erhalten Sie die Sperre des angegebenen Objekts, bevor Sie den Synchronisationscode eingeben.
Direkt auf die Instanzmethode einwirken: Es ist gleichbedeutend mit der Verriegelung der aktuellen Instanz. Bevor Sie den Synchronisationscode eingeben, müssen Sie die Sperre der aktuellen Instanz erhalten.
Direkt auf statische Methoden einwirken: Es ist gleichbedeutend mit der Verriegelung der aktuellen Klasse. Bevor Sie den Synchroncode eingeben, müssen Sie die Sperre der aktuellen Klasse erhalten.
Wenn es auf der Instanzmethode funktioniert, machen Sie keine neuen zwei verschiedenen Instanzen
Es funktioniert nach statischen Methoden, solange die Klasse gleich ist, da das zusätzliche Schloss eine Klasse -Klasse ist und zwei verschiedene Instanzen neu sein können.
Wie man wartet und benachrichtigt:
Verwenden Sie die Schloss, rufen Sie Warte auf und benachrichtigen Sie
Ich werde in diesem Artikel nicht auf Details eingehen.