© Excellent backgrounds/Shutterstock.com
Kolumne: Java-Trickkiste

Pleiten, Pech und Pannen mit Threads


Zur letzten Ausgabe der Java-Trickkiste kam als Feedback die Bitte, das Verhalten von volatile genauer zu erklären. Also gibt es diesen Monat ein paar überraschende Codebeispiele mit Erklärungen. Früher bedeutete Multi-Threading, dass mehrere Threads auf den (potenziell) selben Daten arbeiteten. Das Betriebssystem konnte zu jedem Zeitpunkt den aktuell laufenden ­Thread unterbrechen und einen anderen aktivieren.

Schalter für Hotspot

Dabei konnte es Race Conditions geben, und nebenläufiger Code war schon damals kompliziert zu schreiben, zu verstehen und erst recht zu debuggen. Aber die Welt funktionierte so, wie man es erwartete. Wenn ein Thread einen Wert in den Speicher schrieb und ein anderer ihn las, dann war entweder der Schreibzugriff vor dem Lesezugriff oder umgekehrt.

Und wenn eine Variable volatile war, erzwang das einfach, dass ihre Werte direkt in den Speicher geschrieben wurden, statt z. B. zuerst eine Weile in einem Prozessorregister geparkt zu werden.

Die Welt ist kompliziert

Diese gute alte Zeit ist spätestens seit Multicore-Prozessoren und Java 5 endgültig vorbei. Und die oben skizzierte naive Sicht auf Multi-Threading reicht nicht mehr aus, um das Verhalten von Programmen zu verstehen. Mehrere Cores führen Code tatsächlich gleichzeitig aus. Es gibt mehrstufige CPU-Caches, und wenn ein Core einen Wert in den „Speicher“ schreibt, landet er zunächst im Cache – und wenn ein anderer Core später die „selbe“ Speicherzelle liest, heißt das noch lange nicht, dass er den aktuellen, geänderten Wert sieht.

Und moderne CPUs „raten“ bei Fallunterscheidungen, welcher Zweig ausgeführt wird, und führen Code spekulativ aus – und machen die Änderung später rückgängig, wenn sie sich vertan haben. So können vorübergehend Werte im Speicher stehen, die „eigentlich“ nie hätten entstehen können. Und moderne Compiler ändern die Reihenfolge von Instruktionen, um bessere Performance zu erreichen.

Alle diese Effekte machen Programme deutlich schneller, und es wäre dumm, sich die „gute alte Zeit“ zurückzuwünschen. Aber sie machen die Welt für uns Entwickler komplizierter – und natürlich auch spannender. Das sollen die folgenden Codebeispiele illustrieren. Vorher aber noch ein Hinweis: Die Java-Spezifikation sagt, was das JDK optimieren darf, und nicht, was es tatsächlich optimiert [1]. Der Beispielcode ist absichtlich fehlerhaft und darf sich deshalb merkwürdig verhalten. Die konkreten Merkwürdigkeiten treten in meiner Umgebung auf: Intel i7 Quad-Core, Oracle Java 8,...

Neugierig geworden?

Angebote für Teams

Für Firmen haben wir individuelle Teamlizenzen. Wir erstellen Ihnen gerne ein passendes Angebot.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang