Technischer Projektleiter
Java Multithreading
Das Ziel dieses Artikels ist es, dich mit Java Multithreading – der Mehrfadenverarbeitung in Java – vertraut zu machen, seine Prinzipien, Vorteile, aber auch Nachteile zu erklären.
Multitasking – was es ist
Multitasking ermöglicht auf einem Computer das gleichzeitige Ausführen mehrerer Aktivitäten. Zum Beispiel. mehrere Programme, die parallel auf einem Computer laufen, wobei der Programmierer gleichzeitig Code kompiliert und Musik hört, während das Betriebssystem im Hintergrund aktualisiert wird.
Multitasking kann auf Prozessen und Threads basieren. Threads können Teil von Prozessen sein, und diese ermöglichen es, dass Teile desselben Programms (Prozesses) gleichzeitig auf einem Computer ausgeführt werden.
Thread vs. Prozess
– Threads teilen sich denselben Adressraum
-der Overhead beim Umschalten zwischen Threads ist normalerweise geringer als zwischen Prozessen.
– die Kommunikationsanforderungen zwischen Threads sind relativ gering
Multithreading – warum verwenden man es?
In einer Single-Thread-Umgebung kann immer nur eine Aufgabe gleichzeitig laufen. Ein Beispiel in Java kann sein main() Methode, bei der der Programmablauf sequentiell ausgeführt wird. Falls auf eine Benutzereingabe gewartet wird, sind die Rechenzyklen der CPU unnötig ungenutzt. In einem Multi-Thread-Programm könnte die Analyse oder Überprüfung von Benutzerdaten jedoch zu solchen Zeiten im Hintergrund durchgeführt werden.
Ebenso können Single-Thread-GUI-Programme, die denselben Thread für zeitaufwändige Berechnungen auf dem Prozessor verwenden, die GUI während der Berechnung einfrieren und so das Benutzererlebnis beeinträchtigen.
Threads – Fäden
Ein Thread im Programm wird sequentiell und unabhängig von den anderen ausgeführt. So können viele Threads, die sich einen gemeinsamen Adressraum (Speicher) teilen, während der Programmausführung gleichzeitig laufen und somit Daten austauschen.
Java Threads – Fäden in Java
Es gibt 3 Konzepte im Zusammenhang mit Multithreading in Java:
1. Erstellen von Threads und Bereitstellen des Codes, der von dem Thread ausgeführt werden soll
2. Zugriff auf gemeinsame Daten und Code über Synchronisation
3. Übergang zwischen verschiedenen Filamentzuständen
Java main thread – Haupt-Faden
Nachdem das Java-Programm ausgeführt wurde, wird ein Benutzer-Thread erstellt, um main() Methode der Anwendung auszuführen. Dieser Thread wird auch als der Hauptthread bezeichnet. Wenn während der Ausführung keine weiteren Threads erstellt werden, wird das Programm nach der Ausführung beendet. Vom Haupt-Thread aus werden weitere Threads erstellt, und das Programm läuft nach Beendigung der Hauptmethode weiter, bis alle Benutzer-Threads beendet sind.
Einen Thread erstellen
In Java wird ein Thread durch ein Objekt der Klasse Thread dargestellt. Der Thread kann auf eine von zwei Arten erstellt werden:
1. durch Implementierung der Schnittstelle java.lang.Runnable
Durch Vererbung von der Klasse java.lang.Thread
Synchronisierung von Threads
Threads können Ressourcen gemeinsam nutzen, da sie denselben Adressraum teilen. Es gibt jedoch kritische Situationen, in denen es am besten ist, wenn jeweils nur ein Thread Zugriff auf gemeinsame Ressourcen hat. Ein Beispiel wäre die Bestellung von Konzertkarten in einem Informationssystem, bei dem die Kunden miteinander konkurrieren und jeder versucht, den besten Platz zu bekommen, aber nur einer von ihnen kann jede Karte bestellen. Beim eigentlichen Kauf eines Tickets muss also nur ein einziger Kunde Zugriff auf dieses Ticket haben.
Regeln für die Synchronisierung von Threads
Ein Thread muss eine Sperre für eine gemeinsam genutzte Ressource erwerben, bevor er sie verwenden kann. Die Systemlaufzeit stellt sicher, dass kein anderer Thread auf eine gemeinsam genutzte Ressource zugreifen kann, wenn ein anderer Thread bereits eine Sperre für diese Ressource erworben hat.
Wenn ein Thread nicht sofort eine Sperre erhalten kann, ist er gesperrt, d.h. er muss warten, bis eine Sperre verfügbar wird. Wenn ein Thread eine gemeinsam genutzte Ressource verlässt, sorgt die Systemlaufzeit dafür, dass die Sperre aufgehoben wird. Wenn ein Thread auf den Zugriff wartet, kann dieser Thread den Zugriff erhalten, nachdem die Sperre aufgehoben wurde.
Das Programm selbst kann keine Rückschlüsse auf die Reihenfolge der Threads ziehen, denen die Sperre gewährt wird, da dies nicht vom Programmierer, sondern vom Betriebssystem entschieden wird. Ohne die richtige Synchronisierung würden zwei oder mehr Threads denselben Wert gleichzeitig aktualisieren und ihn in einem undefinierten oder inkonsistenten Zustand belassen.
Methoden der Synchronisierung
Wenn sich ein Thread innerhalb eines synchronisierten Methodenobjekts befindet, müssen alle anderen Threads, die den Code der synchronisierten Methode ausführen müssen, ebenfalls warten. Diese Einschränkung gilt nicht für einen Thread, der eine Sperre erworben hat und eine synchronisierte Objektmethode ausführt.
Eine solche Methode kann weiterhin andere synchronisierte Methoden des Objekts aufrufen, ohne blockiert zu werden. Unsynchronisierte Objektmethoden können jederzeit von anderen Threads aufgerufen werden.
Sicherheit der Fäden
Mit diesem Begriff werden Klassen bezeichnet, die so konzipiert sind, dass der Zustand ihrer Objekte stets konsistent ist, auch wenn die Objekte gleichzeitig von mehreren Threads verwendet werden. Ein Beispiel ist die Klasse StringBuffer.
Lebenszyklus von Java-Threads
Das folgende Diagramm zeigt alle möglichen Zustände, die Threads in Java durchlaufen.
Neu – der Status eines neu erstellten Threads, der noch nicht mit der Ausführung begonnen hat.
Ausführbar – wird entweder ausgeführt oder ist zur Ausführung bereit, wartet aber noch auf die Zuweisung von Ressourcen.
Blockiert – wartet darauf, eine Sperre zu erhalten, um eine synchronisierte Methode oder einen Block zu betreten.
Warten – wartet auf einen anderen Thread, der eine Aktion ausführt, ohne Zeitlimit.
Zeitgesteuertes Warten – dasselbe wie Warten, aber mit einem Zeitlimit.
Beendet – der Thread hat seine Ausführung beendet.
Wartestatus
Ein Thread in einem schwebenden Zustand kann aufgeweckt werden, wenn eines dieser drei Ereignisse eintritt:
1. Ein anderer Thread ruft die Methode notify() für das Objekt des wartenden Threads auf, und der wartende Thread wird als aufzuweckender Thread ausgewählt.
2. Die Wartezeit des Threads läuft ab.
3. Ein anderer Thread unterbricht den wartenden Thread.
Der Zustand der Wiederbelebung
Der Aufruf der Methode notify() für ein Objekt weckt einen der Threads auf, die darauf warten, eine Sperre für dieses Objekt zu erhalten. Die Auswahl des Threads, der aufgeweckt werden soll, hängt von der von der JVM implementierten Thread-Verwaltung ab. Der aufgeweckte Thread befindet sich nicht sofort in einem ausführungsbereiten Zustand, sondern wechselt zunächst den Zustand, um die Sperre zu erhalten. Der Thread wird auch aus der Liste der anhängigen Threads für dieses Objekt entfernt.
Verfallstatus
Ein Aufruf der wait() -Methode legt fest, wie lange es dauert, bis der Thread abläuft, wenn er nicht durch eine Benachrichtigung geweckt wird.
Status der Unterbrechung
Tritt auf, wenn ein anderer Thread die interrupt() -Methode des wartenden Threads aufruft. Der aufgeweckte Thread wird aktiviert, aber der Rückgabewert der wait() -Methode führt zu einer IntteruptedException-Ausnahme, sobald der aufgeweckte Thread an der Reihe ist. Der Code, der diese Ausnahme auslöst, muss bereit sein, sie zu behandeln.
Thread-Prioritäten – Thread-Prioritäten
Prioritäten sind ganzzahlige Werte von 1 (die niedrigste Priorität, die durch die Konstante Thread.MIN_PRIORITY gegeben ist) bis 10 (die höchste Priorität, die durch die Konstante Thread.MAX_PRIORITY gegeben ist). Der Standardwert ist 5 (Thread.NORM_PRIORITY). Ein Thread erbt seine Priorität von seinem übergeordneten Thread. Die Thread-Priorität kann mit der Methode setPriority() festgelegt und mit der Methode getPriority() abgefragt werden, die beide in der Klasse Thread definiert sind. Es sollte betont werden, dass die Prioritätseinstellung eine Empfehlung für die Java Virtual Machine (JVM) ist und die JVM ihr nicht folgen muss.
Deadlock
Dies ist eine Situation, in der ein Thread auf eine Sperre wartet, die von einem anderen Thread gehalten wird, und dieser Thread auf eine andere Sperre wartet, die vom ersten Thread gehalten wird.
Die wichtigsten Methoden der Klasse Thread
start() Initialisiert den Thread und ruft intern die Methode run() auf.
run() Die auszuführende Aufgabe wird in dieser Methode definiert.
sleep() Der laufende Prozess wird für eine bestimmte Zeitspanne angehalten.
yield() bricht die Ausführung des aktuellen Threads ab und ermöglicht die Ausführung eines anstehenden anderen Threads mit der gleichen oder einer höheren Priorität.
stop() Beendet die Ausführung des Threads dauerhaft.
join() Der Thread wartet darauf, dass ein anderer Thread die Ausführung beendet.
isAlive() Prüft, ob der Thread noch am Leben ist oder nicht. Gibt einen booleschen Wert (true/false) zurück, der angibt, ob der Thread läuft oder nicht.
setPriority() Setzt die Priorität des Threads.
Vorteile von Java Multithreading
Multithreading in Java verbessert die Leistung und Zuverlässigkeit, während es gleichzeitig die Zeit, die für die Ausführung eines Programms benötigt wird, drastisch reduziert und damit die Betriebskosten der Software senkt. Der Prozessor und andere Hardwareressourcen werden so effizienter genutzt.
Nachteile von Java Multithreading
Multithreading in Java erhöht die Komplexität beim Debuggen von Code und erhöht gleichzeitig die Wahrscheinlichkeit eines Deadlocks bei der Prozessausführung. Die Reihenfolge der Ausführung von Threads liegt nicht in der Hand des Programmierers, so dass jede Ausführung des Programms einen anderen Verlauf nehmen kann. Auch bei der Portierung des Programms auf andere Plattformen kann es zu Komplikationen kommen.
Multitasking vs. Multithreading
Der Hauptunterschied zwischen Multitasking und Multithreading besteht darin, dass beim Multitasking mehrere unabhängige Prozesse oder Aufgaben ausgeführt werden, während beim Multithreading ein einzelner Prozess in mehrere Threads aufgeteilt wird, die gleichzeitig ausgeführt werden können. Multitasking wird verwendet, um mehrere Prozesse zu verwalten, während Multithreading verwendet wird, um die Leistung eines einzelnen Prozesses zu verbessern.
Multitasking vs. Multiprozessing
Der Unterschied zwischen Multitasking und Multiprocessing besteht darin, dass beim Multitasking mehrere unabhängige Prozesse oder Aufgaben auf einem einzigen Prozessor ausgeführt werden, während beim Multiprocessing mehrere Prozessoren zur gleichzeitigen Ausführung mehrerer Prozesse verwendet werden. Multitasking wird verwendet, um mehrere Prozesse auf einem einzigen Prozessor zu verwalten, während Multiprocessing verwendet wird, um die Systemleistung zu verbessern, indem mehrere Prozesse gleichzeitig auf mehreren Prozessoren ausgeführt werden können.
Multiprogramming vs Multitasking vs Multithreading vs Multiprocessing
Tippe auf das Bild der Tabelle, um es auf deinem mobilen Gerät korrekt anzuzeigen.
Multiprogramming | Multitasking | Multithreading | Multiprocessing | |
---|---|---|---|---|
Definition | Mehrere Programme auf einer einzigen CPU ausführen | Ausführen mehrerer Aufgaben (Anwendungen) auf einer einzigen CPU | Starten mehrerer Threads innerhalb einer einzigen Aufgabe (Anwendung) | Ausführen mehrerer Prozesse auf mehreren Prozessoren (oder Kernen) |
Ressourcen teilen | Ressourcen (CPU, Speicher) werden von Programmen gemeinsam genutzt | Ressourcen (CPU, Speicher) werden von Aufgaben gemeinsam genutzt | Ressourcen (CPU, Speicher) werden von Threads gemeinsam genutzt | Jeder Prozess verfügt über seine eigenen Ressourcen (CPU, Speicher) |
Planung | Verwendet Round-Robin- oder prioritätsbasiertes Scheduling, um den Programmen CPU-Zeit zuzuweisen | Verwendet die prioritätsbasierte Planung oder Zeitsegmentierung, um den Aufgaben CPU-Zeit zuzuweisen. | Verwendet prioritätsbasiertes Scheduling oder Zeitsegmentierung, um den Threads CPU-Zeit zuzuweisen | Jeder Prozess kann seinen eigenen Zeitplanungsalgorithmus haben |
Speicherverwaltung | Jedes Programm hat seinen eigenen Speicherplatz | Jede Aufgabe hat ihren eigenen Speicherplatz | Threads teilen sich den Speicherplatz innerhalb einer Aufgabe | Jeder Prozess hat seinen eigenen Speicherplatz |
Kontext umschalten | Erfordert Kontextwechsel, um zwischen Programmen zu wechseln | Erfordert Kontextwechsel, um zwischen Aufgaben zu wechseln | Erfordert Kontextwechsel, um zwischen Threads zu wechseln | Erfordert Kontextwechsel, um zwischen Prozessen zu wechseln |
Kommunikation zwischen Prozessen (IPC) | Verwendet Messaging oder gemeinsamen Speicher für IPC | Verwendet Messaging oder gemeinsamen Speicher für IPC | Verwendet Thread-Synchronisierungsmechanismen (z.B. Sperren, Semaphoren) für IPC | Verwendet prozessübergreifende Kommunikationsmechanismen (z.B. Pipes, Sockets) für IPC |
Andere empfohlene Ressourcen
Das Ziel dieses Artikels war es, den Leser mit Multithreading in Java vertraut zu machen, seine Prinzipien, Vor- und Nachteile zu erklären. Da dies ein recht komplexes Thema ist, könnte man noch viel mehr darüber schreiben und noch mehr in die Tiefe gehen, am besten mit Beispielen. Ich empfehle Ihnen daher die folgenden Ressourcen, um Ihr Wissen zu erweitern: