1. Parameter an Threads übergeben
Wenn Sie einen Thread starten, möchte man ihm oft eine „Arbeit“ mitgeben: Dateiname, Zahlenbereich oder eine Begrüßung. Ein Thread ohne Parameter ist wie ein Kurier ohne Adresse: Er rennt durch die Stadt, weiß aber nicht, wohin er die Pizza liefern soll.
Wie macht man das?
In Java werden Threads gewöhnlich auf zwei Arten erstellt:
- Von der Klasse Thread erben
- Das Interface Runnable implementieren (oder einen Lambda-Ausdruck verwenden)
Die Parameterübergabe erfolgt meist über den Konstruktor der Klasse, die Runnable implementiert. Das ist einfach, sicher und verständlich. Machen Sie die Felder final – so schützen Sie die Parameter vor Änderungen aus anderen Threads. Setter/öffentliche Felder zu verwenden, ist mit Synchronisationsproblemen behaftet.
Beispiel 1: Thread mit Parameter über den Konstruktor
Wir verbessern eine Beispielanwendung: Verarbeitung von Benutzerbestellungen in einem separaten Thread.
public class OrderProcessor implements Runnable {
private final String orderId;
public OrderProcessor(String orderId) {
this.orderId = orderId;
}
@Override
public void run() {
System.out.println("Verarbeite Bestellung: " + orderId + " in Thread " + Thread.currentThread().getName());
// Hier kann eine langwierige Verarbeitung stattfinden ...
}
}
Wir erstellen und starten Threads:
public class Main {
public static void main(String[] args) {
Thread thread1 = new Thread(new OrderProcessor("ORDER-001"));
Thread thread2 = new Thread(new OrderProcessor("ORDER-002"));
thread1.start();
thread2.start();
}
}
Erwartete Ausgabe:
Verarbeite Bestellung: ORDER-001 in Thread Thread-0
Verarbeite Bestellung: ORDER-002 in Thread Thread-1
Beispiel 2: Thread mit Parameter über einen Lambda-Ausdruck (Java 8+)
Wenn die Aufgabe einfach ist, kommt man ohne eigene Klasse aus.
public class Main {
public static void main(String[] args) {
String user1 = "Alice";
String user2 = "Bob";
Thread thread1 = new Thread(() -> {
System.out.println("Hallo, " + user1 + " aus " + Thread.currentThread().getName());
});
Thread thread2 = new Thread(() -> {
System.out.println("Hallo, " + user2 + " aus " + Thread.currentThread().getName());
});
thread1.start();
thread2.start();
}
}
Warum sollte man keine Setter/Getter zur Parameterübergabe verwenden?
Wenn Sie Parameter über Setter oder öffentliche Felder übergeben, besteht die Gefahr, dass ein anderer Thread den Wert mitten während der Ausführung ändert. Das kann zu schwer zu findenden Fehlern führen. Besser ist es, Felder final zu machen und sie über den Konstruktor zu übergeben.
2. Prioritäten von Threads
In Java hat jeder Thread eine Priorität – eine ganze Zahl von 1 bis 10, die dem Scheduler (Planer) sagt, wie „wichtig“ dieser Thread im Vergleich zu anderen ist. Standardmäßig haben alle Threads die Priorität 5 (Thread.NORM_PRIORITY).
Wichtig: Eine Priorität ist lediglich ein „Hinweis“ für das Betriebssystem. Es ist keine Garantie, dass ein Thread mit Priorität 10 schneller läuft als einer mit Priorität 1. Alles hängt vom Betriebssystem, seinen Einstellungen und der aktuellen Auslastung ab.
Wie setzt man die Priorität eines Threads?
Die Klasse Thread bietet die Methoden:
- setPriority(int newPriority)
- getPriority()
Und drei Standardkonstanten:
- Thread.MIN_PRIORITY (1)
- Thread.NORM_PRIORITY (5)
- Thread.MAX_PRIORITY (10)
Beispiel 3: Priorität für einen Thread setzen
public class PriorityDemo {
public static void main(String[] args) {
Runnable task = () -> {
System.out.println("Thread " + Thread.currentThread().getName() +
" mit Priorität " + Thread.currentThread().getPriority());
};
Thread low = new Thread(task, "LowPriority");
Thread norm = new Thread(task, "NormalPriority");
Thread high = new Thread(task, "HighPriority");
low.setPriority(Thread.MIN_PRIORITY); // 1
norm.setPriority(Thread.NORM_PRIORITY); // 5
high.setPriority(Thread.MAX_PRIORITY); // 10
low.start();
norm.start();
high.start();
}
}
Erwartete Ausgabe (Zeilenreihenfolge nicht garantiert!):
Thread LowPriority mit Priorität 1
Thread HighPriority mit Priorität 10
Thread NormalPriority mit Priorität 5
Beeinflusst die Priorität die Ausführungsreihenfolge?
In den meisten Fällen – nein. Prioritäten können beeinflussen, wie viel CPU-Zeit ein Thread erhält, garantieren aber weder Start- noch Abschlussreihenfolge. Bauen Sie Ihre Programmlogik nicht auf Prioritäten auf – verwenden Sie sie als „weiche“ Hinweise, z. B. damit ein Hintergrundthread den Hauptthread nicht stört.
Tabelle: Konstanten der Thread-Prioritäten
| Konstante | Wert | Beschreibung |
|---|---|---|
|
1 | Niedrigste Priorität |
|
5 | Normale Priorität |
|
10 | Höchste Priorität |
3. Threads benennen
Der Name eines Threads ist eine Art „Spitzname“ in der Multitasking-Welt. Wenn Sie Dutzende Threads haben, ist das Debuggen einfacher, wenn Sie statt "Thread-7" im Log "FileUploader-1" sehen. Das ist besonders wichtig bei der Log-Analyse und Fehlersuche.
Wie weist man einem Thread einen Namen zu?
Den Namen kann man direkt im Konstruktor von Thread angeben oder mit der Methode setName setzen. Den aktuellen Namen erfährt man mit getName.
Thread t = new Thread(() -> {
System.out.println("Ich arbeite!");
}, "MyThreadName");
t.start();
Beispiel 4: Benannte Threads
public class NamedThreads {
public static void main(String[] args) {
Thread threadA = new Thread(() -> {
System.out.println("Ich bin ein Thread: " + Thread.currentThread().getName());
}, "Downloader");
Thread threadB = new Thread(() -> {
System.out.println("Ich bin ein Thread: " + Thread.currentThread().getName());
});
threadB.setName("Uploader");
threadA.start();
threadB.start();
}
}
Erwartete Ausgabe:
Ich bin ein Thread: Downloader
Ich bin ein Thread: Uploader
4. Praxis: mehrere Threads mit unterschiedlichen Parametern, Prioritäten und Namen
Fassen wir alles in einem Beispiel zusammen: Jede Bestellung ist ein eigener Thread mit eigenem Namen und eigener Priorität.
public class OrderProcessor implements Runnable {
private final String orderId;
private final int processingTimeMs;
public OrderProcessor(String orderId, int processingTimeMs) {
this.orderId = orderId;
this.processingTimeMs = processingTimeMs;
}
@Override
public void run() {
System.out.println("[" + Thread.currentThread().getName() + "] Verarbeitung der Bestellung " + orderId +
" gestartet (Priorität " + Thread.currentThread().getPriority() + ")");
try {
Thread.sleep(processingTimeMs); // Arbeit simulieren
} catch (InterruptedException e) {
System.out.println("[" + Thread.currentThread().getName() + "] Unterbrochen!");
}
System.out.println("[" + Thread.currentThread().getName() + "] Verarbeitung der Bestellung " + orderId + " abgeschlossen");
}
public static void main(String[] args) {
Thread fastOrder = new Thread(new OrderProcessor("FAST-ORDER", 500), "FastOrderThread");
Thread normalOrder = new Thread(new OrderProcessor("NORMAL-ORDER", 1000), "NormalOrderThread");
Thread slowOrder = new Thread(new OrderProcessor("SLOW-ORDER", 2000), "SlowOrderThread");
fastOrder.setPriority(Thread.MAX_PRIORITY);
normalOrder.setPriority(Thread.NORM_PRIORITY);
slowOrder.setPriority(Thread.MIN_PRIORITY);
fastOrder.start();
normalOrder.start();
slowOrder.start();
}
}
Was wir sehen:
- Jeder Thread schreibt, welche Bestellung er verarbeitet, seinen Namen und seine Priorität.
- Die Verarbeitungszeit wird mit Thread.sleep simuliert.
- Die Abschlussreihenfolge der Threads muss nicht mit der Priorität übereinstimmen.
5. Wichtige Feinheiten und Besonderheiten
Parameterübergabe: nur über den Konstruktor!
So ist es sicherer. Deklarieren Sie die Parameter als final – sie können nach der Objekterstellung nicht mehr verändert werden, und kein anderer Thread „unterjubelt“ eigene Werte. Das ist entscheidend, um Datenrennen zu verhindern.
Prioritäten: Bauen Sie darauf keine Business-Logik auf
Prioritäten sind wie eine Bitte an den Kellner: „Könnte ich meinen Kaffee etwas schneller bekommen?“. Manchmal funktioniert es, manchmal nicht. Es ist eine Empfehlung an das Betriebssystem, keine Ausführungsregel.
Thread-Namen: für das Debugging verwenden
Spart enorm viel Zeit bei der Log-Analyse. Gewöhnen Sie sich an, sofort aussagekräftige Namen zu vergeben.
6. Häufige Fehler beim Umgang mit Parametern und Prioritäten von Threads
Fehler Nr. 1: Parameter über öffentliche Felder übergeben. Wenn Sie Parameter als gewöhnliche Felder deklarieren und sie nach dem Start des Threads ändern, können unvorhersehbare Ergebnisse auftreten. Verwenden Sie final-Felder und den Konstruktor.
Fehler Nr. 2: Erwarten, dass die Priorität die Reihenfolge garantiert. Sie setzen Thread.MAX_PRIORITY – und denken, der Thread sei immer zuerst? Nein. Verwenden Sie Prioritäten nicht zur Synchronisation oder zur Steuerung der Logik.
Fehler Nr. 3: Unbenannte Threads in Logs. Wenn in den Logs nur „Thread-3“, „Thread-7“ stehen, ist es schwer, den Schuldigen zu finden. Geben Sie Threads sinnvolle Namen mithilfe des Konstruktors oder von setName.
Fehler Nr. 4: Dasselbe Runnable-Objekt für mehrere Threads verwenden. Wenn ein Runnable-Objekt Zustand enthält und Sie es an mehrere Threads übergeben, „teilen“ sie die Parameter – ein direkter Weg zu Datenrennen. Erstellen Sie pro Thread ein eigenes Runnable.
Fehler Nr. 5: Parameter über set-Methoden übergeben. Wenn Sie Parameter über set*-Methoden erst nach der Erstellung/dem Start des Threads setzen, können sich die Werte „on the fly“ ändern – klassische race condition.
GO TO FULL VERSION