Java-Multithreading-Programmierung
Java bietet integrierte Unterstützung für Multithreading-Programmierung. Ein Thread bezieht sich auf einen einzelnen sequenziellen Kontrollfluss innerhalb eines Prozesses. Ein Prozess kann mehrere Threads gleichzeitig ausführen, und jeder Thread führt verschiedene Aufgaben parallel aus.
Multithreading ist eine spezielle Form des Multitaskings, aber Multithreading verursacht einen geringeren Ressourcenaufwand.
Hier ist ein weiterer Begriff im Zusammenhang mit Threads - Prozess: Ein Prozess umfasst vom Betriebssystem zugewiesenen Speicher und enthält einen oder mehrere Threads. Ein Thread kann nicht eigenständig existieren; er muss Teil eines Prozesses sein. Ein Prozess läuft weiter, bis alle Nicht-Daemon-Threads die Ausführung beendet haben.
Multithreading ermöglicht es Programmierern, hocheffiziente Programme zu schreiben, um eine vollständige Nutzung zu erreichen.
Der Lebenszyklus eines Threads
Ein Thread ist ein dynamischer Ausführungsprozess, und er durchläuft ebenfalls einen Prozess von der Erstellung bis zum Tod.
Das folgende Diagramm zeigt den vollständigen Lebenszyklus eines Threads.
- Neuer Zustand:
Unter Verwendung des SchlüsselwortsnewundThreadNachdem eine Klasse oder ihre Unterklasse ein Thread-Objekt erstellt hat, befindet sich das Thread-Objekt im neuen Zustand. Es verbleibt in diesem Zustand, bis das Programmstart()dieser Thread.
- Bereit-Zustand:
Nachdem das Thread-Objekt die Methode start() aufgerufen hat, wechselt der Thread in den Bereit-Zustand. Ein Thread im Bereit-Zustand befindet sich in der Bereit-Warteschlange und wartet auf die Planung durch den Thread-Scheduler in der JVM.
- Laufender Zustand:
Wenn ein Thread im Bereit-Zustand CPU-Ressourcen erhält, kann errun()ausführen; zu diesem Zeitpunkt befindet sich der Thread im laufenden Zustand. Ein Thread im laufenden Zustand ist am komplexesten; er kann in den blockierten Zustand, den Bereit-Zustand und den toten Zustand wechseln.
- Blockierter Zustand:
Wenn ein Thread Methoden wie sleep, suspend usw. ausführt und danach die belegten Ressourcen verliert, wechselt der Thread vom laufenden Zustand in den blockierten Zustand. Wenn die Sleep-Zeit abläuft oder Geräteressourcen erhalten werden, kann er wieder in den Bereit-Zustand eintreten. Er kann in drei Arten unterteilt werden:
Wartend blockiert: Ein Thread im laufenden Zustand führt die Methode wait() aus, wodurch der Thread in den wartend blockierten Zustand übergeht.
Synchron blockiert: Der Thread kann die synchronisierte Sperre nicht erwerben (weil die Sperre von einem anderen Thread belegt ist).
Andere Blockierung: Wenn eine I/O-Anfrage durch Aufrufen von sleep() oder join() des Threads ausgegeben wird, wechselt der Thread in den blockierten Zustand. Wenn der sleep()-Zustand abläuft, wenn join() auf die Beendigung des Threads wartet oder abläuft, oder wenn die I/O-Verarbeitung abgeschlossen ist, wechselt der Thread zurück in den Bereit-Zustand.
- Beendeter Zustand:
Wenn ein laufender Thread seine Aufgabe abschließt oder andere Beendigungsbedingungen eintreten, wechselt der Thread in den beendeten Zustand.
Thread-Priorität
Jeder Java-Thread besitzt eine Priorität, die dem Betriebssystem hilft, die Reihenfolge zu bestimmen, in der Threads geplant werden.
Die Priorität eines Java-Threads ist eine ganze Zahl, deren Wert von 1 (Thread.MIN_PRIORITY) bis 10 (Thread.MAX_PRIORITY) reicht.
Standardmäßig wird jedem Thread eine Priorität von NORM_PRIORITY (5) zugewiesen.
Threads mit höherer Priorität sind für das Programm wichtiger und sollten vor Threads mit niedrigerer Priorität Prozessorressourcen erhalten. Allerdings garantiert die Thread-Priorität nicht die Reihenfolge der Thread-Ausführung und ist stark plattformabhängig.
Erstellen eines Threads
Java bietet drei Möglichkeiten, Threads zu erstellen:
- Durch Implementieren der Runnable-Schnittstelle;
- Durch Erweitern der Thread-Klasse selbst;
- Durch Erstellen von Threads über Callable und Future.
Erstellen eines Threads durch Implementieren der Runnable-Schnittstelle
Um einen Thread zu erstellen, ist der einfachste Weg, eine Klasse zu erstellen, die die Runnable-Schnittstelle implementiert.
Um Runnable zu implementieren, muss eine Klasse nur eine einzige Methode namens run() implementieren, die wie folgt deklariert ist:
Sie können diese Methode überschreiben. Es ist wichtig zu verstehen, dass run() andere Methoden aufrufen, andere Klassen verwenden und Variablen deklarieren kann, genau wie der Haupt-Thread.
Nachdem Sie eine Klasse erstellt haben, die die Runnable-Schnittstelle implementiert, können Sie innerhalb der Klasse ein Thread-Objekt instanziieren.
Thread definiert mehrere Konstruktoren. Der folgende ist derjenige, den wir häufig verwenden:
Hier ist threadOb eine Instanz einer Klasse, die die Runnable-Schnittstelle implementiert, und threadName gibt den Namen des neuen Threads an.
Nachdem der neue Thread erstellt wurde, wird er erst ausgeführt, wenn Sie seine start()-Methode aufrufen.
Das Folgende ist ein Beispiel für das Erstellen eines Threads und dessen Start:
Beispiel
Kompilieren Sie das obige Programm. Das Ausführungsergebnis ist wie folgt:
Creating Thread-1 Starting Thread-1 Creating Thread-2 Starting Thread-2 Running Thread-1 Thread: Thread-1, 4 Running Thread-2 Thread: Thread-2, 4 Thread: Thread-1, 3 Thread: Thread-2, 3 Thread: Thread-1, 2 Thread: Thread-2, 2 Thread: Thread-1, 1 Thread: Thread-2, 1 Thread Thread-1 exiting. Thread Thread-2 exiting.
Erstellen eines Threads durch Erweitern von Thread
Die zweite Möglichkeit, einen Thread zu erstellen, besteht darin, eine neue Klasse zu erstellen, die die Thread-Klasse erweitert, und dann eine Instanz dieser Klasse zu erstellen.
Die erweiternde Klasse muss die Methode run() überschreiben, die der Einstiegspunkt des neuen Threads ist. Sie muss außerdem die Methode start() aufrufen, um ausgeführt zu werden.
Obwohl diese Methode als Ansatz zur Implementierung von Multithreading aufgeführt wird, ist sie im Wesentlichen eine Instanz, die die Runnable-Schnittstelle implementiert.
Beispiel
Kompilieren Sie das obige Programm. Das Ausführungsergebnis ist wie folgt:
Creating Thread-1 Starting Thread-1 Creating Thread-2 Starting Thread-2 Running Thread-1 Thread: Thread-1, 4 Running Thread-2 Thread: Thread-2, 4 Thread: Thread-1, 3 Thread: Thread-2, 3 Thread: Thread-1, 2 Thread: Thread-2, 2 Thread: Thread-1, 1 Thread: Thread-2, 1 Thread Thread-1 exiting. Thread Thread-2 exiting.
Thread-Methoden
Die folgende Tabelle listet einige wichtige Methoden der Thread-Klasse auf:
| Nein. | Methodenbeschreibung |
|---|---|
| 1 |
public void start() Veranlasst diesen Thread mit der Ausführung zu beginnen;JavaDie Java Virtual Machine ruft die run-Methode dieses Threads auf. |
| 2 |
public void run() Wenn dieser Thread unter Verwendung eines separaten Runnable-Objekts erstellt wurde, dann wird die run-Methode dieses Runnable-Objekts aufgerufen; andernfalls tut diese Methode nichts und kehrt zurück. |
| 3 |
public final void setName(String name) Ändert den Namen dieses Threads so, dass er gleich dem Argumentnamen ist. |
| 4 |
public final void setPriority(int priority) Ändert die Priorität dieses Threads. |
| 5 |
public final void setDaemon(boolean on) Markiert diesen Thread als Daemon-Thread oder als Benutzer-Thread. |
| 6 |
public final void join(long millisec) Wartet höchstens millis Millisekunden darauf, dass dieser Thread beendet wird. |
| 7 |
public void interrupt() Unterbricht diesen Thread. |
| 8 |
public final boolean isAlive() Testet, ob dieser Thread aktiv ist. |
Die oben genannten Methoden werden von Thread-Objekten aufgerufen. Die Methoden in der folgenden Tabelle sind statische Methoden der Klasse Thread.
| Nein. | Methodenbeschreibung |
|---|---|
| 1 |
public static void yield() Pausiert das aktuell ausführende Thread-Objekt und führt andere Threads aus. |
| 2 |
public static void sleep(long millisec) Veranlasst den aktuell ausführenden Thread, für die angegebene Anzahl von Millisekunden zu schlafen (Ausführung auszusetzen), abhängig von der Präzision und Genauigkeit des System-Timers und des Schedulers. |
| 3 |
public static boolean holdsLock(Object x) Gibt genau dann true zurück, wenn der aktuelle Thread die Monitor-Sperre für das angegebene Objekt hält. |
| 4 |
public static Thread currentThread() Gibt eine Referenz auf das aktuell ausführende Thread-Objekt zurück. |
| 5 |
public static void dumpStack() Gibt den Stack-Trace des aktuellen Threads im Standardfehlerstrom aus. |
Beispiel
Das folgende ThreadClassDemo-Programm demonstriert einige Methoden der Thread-Klasse:
Code der Datei DisplayMessage.java:
Code der Datei GuessANumber.java:
ThreadClassDemo.java Dateicode:
Das Ausführungsergebnis ist wie folgt; das Ergebnis ist jedes Mal anders.
Starting hello thread... Starting goodbye thread... Hello Hello Hello Hello Hello Hello Goodbye Goodbye Goodbye Goodbye Goodbye .......
Erstellen eines Threads mittels Callable und Future
1. Erstellen Sie eine Implementierungsklasse der Callable-Schnittstelle und implementieren Sie die call()-Methode. Die call()-Methode dient als Ausführungskörper des Threads und hat einen Rückgabewert.
2. Erstellen Sie eine Instanz der Callable-Implementierungsklasse und verwenden Sie die FutureTask-Klasse, um das Callable-Objekt zu umhüllen. Das FutureTask-Objekt kapselt den Rückgabewert der call()-Methode dieses Callable-Objekts.
3. Verwenden Sie das FutureTask-Objekt als Ziel eines Thread-Objekts, um einen neuen Thread zu erstellen und zu starten.
4. Rufen Sie die get()-Methode des FutureTask-Objekts auf, um den Rückgabewert zu erhalten, nachdem der untergeordnete Thread die Ausführung beendet hat.
Beispiel
Vergleich der drei Möglichkeiten zum Erstellen von Threads
1. Wenn Multithreading durch Implementieren der Schnittstellen Runnable oder Callable erstellt wird, implementiert die Thread-Klasse nur die Schnittstelle Runnable oder Callable und kann weiterhin andere Klassen erben.
2. Wenn Multithreading durch Vererben der Thread-Klasse erstellt wird, ist der Code einfach. Wenn Sie auf den aktuellen Thread zugreifen müssen, brauchen Sie nicht Thread.currentThread() zu verwenden; Sie können direkt this verwenden, um den aktuellen Thread zu erhalten.
Einige Schlüsselkonzepte von Threads
In der Multithread-Programmierung müssen Sie die folgenden Konzepte verstehen:
- Thread-Synchronisation
- Kommunikation zwischen Threads
- Thread-Deadlock
- Thread-Steuerung: Aussetzen, Stoppen und Fortsetzen
Verwendung von Multithreading
Der Schlüssel zur effektiven Nutzung von Multithreading liegt darin, zu verstehen, dass Programme nebenläufig statt seriell ausgeführt werden. Wenn ein Programm beispielsweise zwei Teilsysteme hat, die nebenläufig ausgeführt werden müssen, ist an dieser Stelle Multithread-Programmierung erforderlich.
Durch die Verwendung von Multithreading können Sie sehr effiziente Programme schreiben. Beachten Sie jedoch, dass die Ausführungseffizienz des Programms tatsächlich abnimmt statt zunimmt, wenn Sie zu viele Threads erstellen.
Denken Sie daran, dass auch der Overhead durch Kontextwechsel wichtig ist. Wenn Sie zu viele Threads erstellen, verbringt die CPU mehr Zeit mit dem Wechseln von Kontexten als mit der Ausführung des Programms!
Weitere Erweiterungen