Java Thread-Priorität
Java Thread-Prioritäten sind ein Hinweis, keine Garantie — was setPriority wirklich bewirkt, was nicht, und wann man es verwenden sollte.
Thread besitzt eine Priorität — eine ganze Zahl von 1 bis 10. Die JVM übergibt sie dem OS-Scheduler als Hinweis, welchen Thread die CPU als nächstes ausführen soll. Der Hinweis ist genau das — ein Hinweis. Das Betriebssystem kann ihn ignorieren, und auf den meisten Desktop- und Server-Betriebssystemen ist der Effekt irgendwo zwischen subtil und unsichtbar. Dieses Kapitel erklärt, wie die API aussieht, was tatsächlich darunter passiert, und die sehr engen Fälle, in denen das Setzen einer Priorität den Aufwand wert ist.
Die API
public final void setPriority(int newPriority);
public final int getPriority();
public static final int MIN_PRIORITY = 1;
public static final int NORM_PRIORITY = 5;
public static final int MAX_PRIORITY = 10;Drei Konstanten für die Lesbarkeit — der meiste Code, der mit Prioritäten arbeitet, verwendet diese anstelle von rohen Ganzzahlen:
Thread t = new Thread(this::housekeeping, "gc-poker");
t.setPriority(Thread.MIN_PRIORITY); // 1 — "background"
t.start();
Thread h = new Thread(this::handleRequest, "http");
h.setPriority(Thread.NORM_PRIORITY); // 5 — default
h.start();Der Konstruktor verwendet die Priorität des übergeordneten Threads und die maximale Priorität der Thread-Gruppe des übergeordneten Threads — normalerweise 5. Das Setzen auf einen Wert außerhalb von 1..10 wirft eine IllegalArgumentException.
Was „Priorität" für die JVM bedeutet
Zwei Dinge, von denen keines „dieser Thread wird zuerst ausgeführt" bedeutet:
- Ein nativer Scheduling-Hinweis. Die JVM übersetzt die Zahl 1–10 in etwas, das das Host-Betriebssystem versteht. Unter Linux ist das ein
nice-Wert überpthread_setschedparam; unter Windows ist es eineTHREAD_PRIORITY_*-Konstante. Die Zuordnung ist implementierungsdefiniert in der JVM. - Eine maximale Obergrenze pro Thread-Gruppe. Ein Thread kann nicht auf eine Priorität gesetzt werden, die höher ist als die
maxPriorityseiner Thread-Gruppe. Thread-Gruppen sind größtenteils veraltet, aber die Obergrenze gilt noch immer.
Was Priorität nicht bedeutet:
- Es ist kein „Lock" — ein Thread mit hoher Priorität kann keinen Thread innerhalb eines kritischen Abschnitts verdrängen.
- Es ist keine Garantie für die Reihenfolge. Zwei Threads mit Priorität 10 konkurrieren immer noch; einer von ihnen erhält die CPU zuerst.
- Es beeinflusst nicht die Korrektheit. Jedes Programm, das „nur funktioniert", weil es auf Prioritäten angewiesen ist, hat einen echten Synchronisierungsfehler, der sich dahinter verbirgt.
Was das Betriebssystem tatsächlich tut
Verschiedene Host-Betriebssysteme behandeln den Hinweis unterschiedlich. Das aktuelle Verhalten, grob zusammengefasst:
- Linux. Die Standard-JVM-Konfiguration behandelt Java-Prioritäten als
nice-Werte.niceist ein Hinweis an den CFS-Scheduler bezüglich des CPU-Anteils. Nicht-Root-Benutzer können die Priorität nur senken (positivesnice); sie zu erhöhen (negativesnice) erfordertCAP_SYS_NICE, das die meisten JVMs nicht besitzen. In der Praxis istsetPriority(MAX_PRIORITY)aus einem Java-Prozess ohne Root-Rechte oft ein No-Op. - Windows. Prioritäten werden auf die sieben Windows-Thread-Prioritäten abgebildet. Die Abbildung ist aggressiver; Threads mit hoher Priorität erhalten tatsächlich mehr CPU. Aber man kann immer noch keinen Thread innerhalb eines Syscalls oder der ein Kernel-Lock hält, verdrängen.
- macOS. Verwendet
pthread_setschedparamwie Linux; ähnliches Verhalten.
Die Schlussfolgerung ist auf allen gleich: Prioritäten sind Empfehlungen, und unter Linux wird die Empfehlung oft komplett ignoriert. Verlasse dich nicht darauf.
Wann man setPriority tatsächlich verwenden sollte
Drei legitime Verwendungszwecke, in der Reihenfolge, wie oft sie vorkommen:
- Einen Hintergrund-Thread auf
MIN_PRIORITYsetzen, damit das Betriebssystem ihn unter Last sanft deprioritisiert. Ein Telemetrie-Uploader, ein Log-Flusher, ein Cache-Rebuild-Job — keiner dieser sollte jemals benutzerseitige Latenz verursachen. Sie auf 1 zu setzen ist ein kostenloser Hinweis, der dem Betriebssystem sagt: „Dieser kann ruhig ein wenig ausgehungert werden." - Einen Echtzeit-Helfer auf
MAX_PRIORITYsetzen in einer JVM, der das Privileg dazu gewährt wurde. Selten außerhalb von Audio, Game-Loops oder spezialisierten Low-Latency-Systemen. - Nichts tun. Der Standard von
NORM_PRIORITYist die richtige Antwort für fast jeden Thread, den man jemals erstellen wird.
Was man stattdessen verwenden sollte
Wenn der Grund, aus dem man über Prioritäten nachdenkt, einer dieser ist — gibt es ein besseres Werkzeug:
| Ziel | Stattdessen verwenden |
|---|---|
| „Lange Jobs sollten kurze Jobs nicht blockieren" | Zwei ExecutorServices — ein kleiner für latenzempfindliche Arbeit, ein größerer für Batch-Aufgaben |
| „Thread A muss vor Thread B ausgeführt werden" | join, ein CountDownLatch oder eine CompletableFuture-Kette |
| „Nur ein Thread zur gleichen Zeit in dieser Methode" | synchronized oder ein ReentrantLock |
| „Aushungern von niedrig priorisierten Aufgaben vermeiden" | Eine faire Warteschlange (new LinkedBlockingQueue<>() mit einem Lotsystem oder ein PriorityBlockingQueue mit expliziter Aufgabenpriorität — nicht Thread-Priorität) |
Das Muster: Priorität ist ein Scheduling-Hinweis zwischen laufbereiten Threads. Das richtige Werkzeug für „welcher Thread zuerst läuft" ist fast immer Synchronisierung, nicht Priorität.
Prioritätsumkehr
Das klassische Fehlermuster von prioritätsbasiertem Scheduling:
- Ein Thread
Lmit niedriger Priorität erwirbt LockM. - Ein Thread
Hmit hoher Priorität versuchtMzu erwerben und blockiert, weil er aufLwartet. - Ein Thread
Medmit mittlerer Priorität führt CPU-gebundene Arbeit aus und verhindert, dassLeingeplant wird. Hist effektiv auf der Priorität vonMed— umgekehrt.
Java löst das nicht für einen. Das nice-System des Kernels auch nicht. Die Lösung ist entweder (a) sich nicht auf Prioritäten für Korrektheit zu verlassen, oder (b) ReentrantLock mit einer Fair-Ordering-Richtlinie und kurzen kritischen Abschnitten zu verwenden, damit das Umkehrungsfenster begrenzt ist. Wir werden Fairness im Kapitel ReentrantLock kennenlernen.
Thread-Gruppen (größtenteils historisch)
ThreadGroup gp = new ThreadGroup("workers");
gp.setMaxPriority(7);
Thread t = new Thread(gp, runnable, "worker");
t.setPriority(10); // capped to 7 by the groupThread-Gruppen stammen aus Java 1.0 und waren ursprünglich das „Control Panel" für Batches von Threads. Sie wurden fast vollständig durch ExecutorService ersetzt, und die meisten ihrer Methoden sind veraltet. Man sieht ThreadGroup in Stack-Traces und Dumps; typischerweise erstellt man keine selbst. Das einzig noch aktive Element ist die maxPriority-Obergrenze pro Gruppe.
Ein praktisches Beispiel: Prioritäten in Aktion beobachten
Das folgende Programm startet mehrere CPU-gebundene Threads mit unterschiedlichen Prioritäten und misst, wie viel Arbeit jeder in einem festen Zeitfenster erledigt hat. Unter Linux werden alle wahrscheinlich ähnliche Arbeitsmengen erledigen — das ist der Punkt. Unter Windows kann man einen bedeutenden Unterschied sehen. So oder so ist die Lektion dieselbe.
Was man aus dem Lauf mitnehmen kann:
- Alle drei Threads haben höchstwahrscheinlich ähnliche Arbeitsmengen erledigt, obwohl einer mit Priorität 1 und ein anderer mit Priorität 10 lief. Auf einer Linux JVM, die als normaler Benutzer läuft, kann die JVM die Priorität nicht wirklich über den Standard anheben — der Aufruf
setPriority(10)hat das Feld gesetzt, aber das Betriebssystem hat ihn als normale Priorität behandelt. Das Java-Feld ändert sich; das tatsächliche Scheduling kaum. - Der
getPriority()-Aufruf spiegelte den gesetzten Wert wider, unabhängig davon, ob das Betriebssystem ihn berücksichtigt hat. Das ist wichtig beim Debuggen: das Feld zeigt, was der Code angefordert hat, nicht was das Betriebssystem tat. - Die CPU-Schleife hat absichtlich nie geblockt (kein
Thread.sleep, keinwait, kein I/O). Das ist das einzige Szenario, in dem Priorität plausibel eine Rolle spielen kann — reine CPU-Konkurrenz. Sobald ein Thread blockiert, wird die Priorität irrelevant; der blockierte Thread befindet sich ohnehin nicht auf einer CPU. setPriority(11)hatIllegalArgumentExceptiongeworfen. Die Grenzen werden auf der Java-Seite durchgesetzt. Priorität 0 wirft ebenfalls; der gültige Bereich ist genau 1 bis 10.- Die richtige Schlussfolgerung aus dem kleinen (oder nicht vorhandenen) Unterschied in der Batch-Anzahl ist: wenn das Design anfängt, so zu wirken, als ob es Prioritäten für die Korrektheit benötigt, sollte man es durch explizite Synchronisierung ersetzen. Zwei
ExecutorServices verwenden, wenn man Isolation möchte; Locks verwenden, wenn man Reihenfolge möchte; Warteschlangen verwenden, wenn man Fairness möchte. Priorität ist ein letztes Mittel, und unter Linux ist es kaum ein Mittel überhaupt.
Was als nächstes kommt
Das nächste Kapitel, Java Synchronization, beginnt die eigentliche Geschichte von sicherem nebenläufigem Code — das synchronized-Schlüsselwort, intrinsische Monitore und wie Javas erstklassiges Lock-Primitiv funktioniert.