Java Multithreading meistern: Effiziente Nebenläufigkeitsprogrammierung
Java ist eine multithreaded Programmiersprache, was bedeutet, dass wir mehrthreadige Programme mit Java entwickeln können. Ein mehrthreadiges Programm enthält zwei oder mehr Teile, die gleichzeitig ausgeführt werden können und jeweils eine andere Aufgabe zur gleichen Zeit bewältigen – was eine optimale Nutzung der verfügbaren Ressourcen ermöglicht, besonders wenn Ihr Computer über mehrere CPUs verfügt.
Per Definition ist Multitasking der Zustand, bei dem mehrere Prozesse gemeinsame Verarbeitungsressourcen wie eine CPU teilen. Multithreading erweitert das Konzept des Multitasking auf Anwendungen, bei denen Sie spezifische Operationen innerhalb einer einzelnen Anwendung in einzelne Threads unterteilen können. Jeder dieser Threads kann parallel laufen. Das Betriebssystem teilt die Verarbeitungszeit nicht nur zwischen verschiedenen Anwendungen auf, sondern auch zwischen jedem Thread innerhalb einer Anwendung.
Multithreading ermöglicht es Ihnen, Code so zu schreiben, dass mehrere Aktivitäten gleichzeitig im selben Programm ablaufen können.
Lebenszyklus eines Threads
Ein Thread durchläuft verschiedene Phasen in seinem Lebenszyklus. Zum Beispiel wird ein Thread geboren, gestartet, läuft und stirbt dann. Die folgende Abbildung zeigt den vollständigen Lebenszyklus eines Threads.

Dies sind die Phasen des Lebenszyklus:
New (Neu) – Ein neuer Thread beginnt seinen Lebenszyklus im Zustand „New“. Er verbleibt in diesem Zustand, bis das Programm den Thread startet. Er wird auch als geborener Thread bezeichnet.
Runnable (Ausführbar) – Nachdem ein neu geborener Thread gestartet wurde, wird er „Runnable“. Ein Thread in diesem Zustand wird als ausführend seiner Aufgabe betrachtet.
Waiting (Wartend) – Manchmal wechselt ein Thread in den Wartzustand, während er auf einen anderen Thread wartet, der eine Aufgabe ausführt. Ein Thread kehrt nur in den ausführbaren Zustand zurück, wenn ein anderer Thread den wartenden Thread signalisiert, die Ausführung fortzusetzen.
Timed Waiting (Zeitlich begrenztes Warten) – Ein ausführbarer Thread kann für ein angegebenes Zeitintervall in den zeitlich begrenzten Wartzustand eintreten. Ein Thread in diesem Zustand kehrt in den ausführbaren Zustand zurück, wenn das Zeitintervall abläuft oder das Ereignis eintritt, auf das er wartet.
Terminated (Beendet/Dead) – Ein ausführbarer Thread tritt in den beendeten Zustand ein, wenn er seine Aufgabe abgeschlossen hat oder anderweitig beendet wird.
Thread-Prioritäten
Jeder Java-Thread hat eine Priorität, die dem Betriebssystem hilft, die Reihenfolge festzulegen, in der Threads geplant werden.
Java-Thread-Prioritäten liegen im Bereich zwischen MIN_PRIORITY (Konstante 1) und MAX_PRIORITY (Konstante 10). Standardmäßig erhält jeder Thread die Priorität NORM_PRIORITY (Konstante 5).
Threads mit höherer Priorität sind für ein Programm wichtiger und sollten vor Threads mit niedrigerer Priorität Prozessorzeit erhalten. Allerdings können Thread-Prioritäten die Ausführungsreihenfolge nicht garantieren und sind stark plattformabhängig.
Thread durch Implementieren des Runnable-Interfaces erstellen
Wenn Ihre Klasse als Thread ausgeführt werden soll, können Sie dies erreichen, indem Sie das Runnable-Interface implementieren. Sie müssen drei grundlegende Schritte befolgen:
Schritt 1
Als ersten Schritt müssen Sie die run()-Methode implementieren, die vom Runnable-Interface bereitgestellt wird. Diese Methode stellt den Einstiegspunkt für den Thread dar und Sie platzieren Ihre komplette Geschäftslogik in dieser Methode. Folgende ist die einfache Syntax der run()-Methode:
public void run()
Schritt 2
Als zweiten Schritt instanziieren Sie ein Thread-Objekt mit dem folgenden Konstruktor:
Thread(Runnable threadObj, String threadName);
Wobei threadObj eine Instanz einer Klasse ist, die das Runnable-Interface implementiert, und threadName der Name ist, der dem neuen Thread gegeben wird.
Schritt 3
Sobald ein Thread-Objekt erstellt wurde, können Sie es durch Aufrufen der start()-Methode starten, die einen Aufruf der run()-Methode ausführt. Folgende ist die einfache Syntax der start()-Methode:
void start();
Beispiel
Hier ist ein Beispiel, das einen neuen Thread erstellt und startet:
class RunnableDemo implements Runnable {
private Thread t;
private String threadName;
RunnableDemo(String name) {
threadName = name;
System.out.println("Erstelle " + threadName);
}
public void run() {
System.out.println("Führe aus " + threadName);
try {
for (int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
// Thread schlafen lassen
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("Thread " + threadName + " unterbrochen.");
}
System.out.println("Thread " + threadName + " beendet.");
}
public void start() {
System.out.println("Starte " + threadName);
if (t == null) {
t = new Thread(this, threadName);
t.start();
}
}
}
public class TestThread {
public static void main(String args[]) {
RunnableDemo R1 = new RunnableDemo("Thread-1");
R1.start();
RunnableDemo R2 = new RunnableDemo("Thread-2");
R2.start();
}
}
Ausgabe
Erstelle Thread-1
Starte Thread-1
Erstelle Thread-2
Starte Thread-2
Führe aus Thread-1
Thread: Thread-1, 4
Führe aus 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 beendet.
Thread Thread-2 beendet.
Thread durch Erweitern der Thread-Klasse erstellen
Der zweite Weg, einen Thread zu erstellen, besteht darin, eine neue Klasse zu erstellen, die die Thread-Klasse erweitert. Dieser Ansatz bietet mehr Flexibilität bei der Behandlung mehrerer Threads, die mit den in der Thread-Klasse verfügbaren Methoden erstellt wurden.
Schritt 1
Sie müssen die run()-Methode überschreiben, die in der Thread-Klasse verfügbar ist. Diese Methode stellt den Einstiegspunkt für den Thread dar. Syntax:
public void run()
Schritt 2
Sobald das Thread-Objekt erstellt ist, können Sie es durch Aufrufen der start()-Methode starten. Syntax:
void start();
Beispiel
class ThreadDemo extends Thread {
private Thread t;
private String threadName;
ThreadDemo(String name) {
threadName = name;
System.out.println("Erstelle " + threadName);
}
public void run() {
System.out.println("Führe aus " + threadName);
try {
for (int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("Thread " + threadName + " unterbrochen.");
}
System.out.println("Thread " + threadName + " beendet.");
}
public void start() {
System.out.println("Starte " + threadName);
if (t == null) {
t = new Thread(this, threadName);
t.start();
}
}
}
public class TestThread {
public static void main(String args[]) {
ThreadDemo T1 = new ThreadDemo("Thread-1");
T1.start();
ThreadDemo T2 = new ThreadDemo("Thread-2");
T2.start();
}
}
Wichtige Thread-Methoden
Die folgende Liste zeigt die wichtigsten Methoden der Thread-Klasse:
| Nr. | Methode & Beschreibung |
|---|---|
| 1 | public void start() – Startet den Thread in einem separaten Ausführungspfad und ruft run() auf. |
| 2 | public void run() – Wird auf dem Runnable-Objekt aufgerufen, falls der Thread mit einem separaten Runnable-Target instanziiert wurde. |
| 3 | public final void setName(String name) – Ändert den Namen des Thread-Objekts. Es gibt auch getName() zum Abrufen. |
| 4 | public final void setPriority(int priority) – Setzt die Priorität (Werte 1-10). |
| 5 | public final void setDaemon(boolean on) – Markiert den Thread als Daemon-Thread. |
| 6 | public final void join(long millisec) – Blockiert den aktuellen Thread, bis der zweite Thread endet oder die Zeit abläuft. |
| 7 | public void interrupt() – Unterbricht den Thread, falls er blockiert war. |
| 8 | public final boolean isAlive() – Gibt true zurück, wenn der Thread lebt (nach start() bis vor Beendigung). |
Die folgenden Methoden sind statisch und operieren auf dem aktuell laufenden Thread:
| Nr. | Methode & Beschreibung |
|---|---|
| 1 | public static void yield() – Gibt die CPU an andere Threads gleicher Priorität ab. |
| 2 | public static void sleep(long millisec) – Blockiert den aktuellen Thread für mindestens die angegebenen Millisekunden. |
| 3 | public static boolean holdsLock(Object x) – Prüft, ob der aktuelle Thread den Lock auf dem gegebenen Objekt hält. |
| 4 | public static Thread currentThread() – Gibt Referenz auf den aktuell laufenden Thread zurück. |
| 5 | public static void dumpStack() – Druckt Stack-Trace für Debugging. |
Beispiel: ThreadClassDemo
// DisplayMessage.java
public class DisplayMessage implements Runnable {
private String message;
public DisplayMessage(String message) { this.message = message; }
public void run() {
while(true) { System.out.println(message); }
}
}
// GuessANumber.java
public class GuessANumber extends Thread {
private int number;
public GuessANumber(int number) { this.number = number; }
public void run() {
int counter = 0, guess = 0;
do {
guess = (int) (Math.random() * 100 + 1);
System.out.println(this.getName() + " ratet " + guess);
counter++;
} while(guess != number);
System.out.println("** Richtig! " + this.getName() + " in " + counter + " Versuchen.**");
}
}
// ThreadClassDemo.java
public class ThreadClassDemo {
public static void main(String[] args) {
Runnable hello = new DisplayMessage("Hallo");
Thread thread1 = new Thread(hello);
thread1.setDaemon(true);
thread1.setName("hallo");
System.out.println("Starte hallo-Thread...");
thread1.start();
Runnable bye = new DisplayMessage("Tschüss");
Thread thread2 = new Thread(bye);
thread2.setPriority(Thread.MIN_PRIORITY);
thread2.setDaemon(true);
System.out.println("Starte tschüss-Thread...");
thread2.start();
System.out.println("Starte thread3...");
Thread thread3 = new GuessANumber(27);
thread3.start();
try { thread3.join(); } catch (InterruptedException e) { System.out.println("Thread unterbrochen."); }
System.out.println("Starte thread4...");
Thread thread4 = new GuessANumber(75);
thread4.start();
System.out.println("main() endet...");
}
}
Wichtige Java-Multithreading-Konzepte
Bei der Multithreading-Programmierung in Java sollten Sie folgende Konzepte beherrschen:
- Thread-Synchronisation
- Inter-Thread-Kommunikation
- Deadlock-Behandlung
- Wichtige Thread-Operationen
Java
- Java Operatoren meistern: Typen, Syntax und praktische Beispiele
- Java Kommentare: Typen, Verwendung und Best Practices
- Java Enhanced For-Schleife meistern: Syntax, Beispiele und Best Practices
- Java Strings meistern: Erstellung, Methoden und Best Practices
- Java-Schnittstellen meistern: Konzepte, Implementierung und Best Practices
- Java anonyme innere Klassen meistern: Definition, Syntax und praktische Beispiele
- Java Try-With-Resources meistern: Automatisches Ressourcenmanagement erklärt
- Java Annotations erklärt: Typen, Platzierung und praktische Beispiele
- Java Assertions: Praktischer Leitfaden zur Fehlererkennung und Code-Reliabilität
- Java Vector verstehen: Verwendung, Funktionen und Vergleich mit ArrayList