10.8 Mit dem Thread verbundene Variablen 

Unterschiedliche Threads können ohne Probleme das gleiche Runnable ausführen, was auch ein übliches Szenario ist, wenn der Programmcode immer der gleiche ist. Doch auch wenn der Programmcode immer gleich bleibt, soll jedem Thread doch vielleicht ein eigener Speicherbereich zugeteilt werden, in dem er Informationen ablegen kann. Falls das Runnable-Objekt selbst eine Objektvariable hätte und pro Thread auf diese Weise ein neues Runnable-Objekt gebildet würde, wäre das kein Problem, doch müsste bei genau einem Runnable-Objekt und beliebig vielen Threads ein anderer Ort gefunden werden.
10.8.1 ThreadLocal 

Die Lösung ist relativ einfach: Es müsste eine Datenstruktur geben, die jeden Current-Thread mit einem Objekt assoziiert. Diese Struktur muss dann im Runnable-Objekt referenzierbar sein. Java bietet mit der Klasse java.lang.ThreadLocal eine Implementierung für thread-lokale Variablen (engl. thread local storage (TLS)) an.
class java.lang.ThreadLocal<T> |
- ThreadLocal() Erzeugt eine neue Thread-lokale Variable.
- void set( T value ) Setzt den Wert für den lokalen Thread.
- T get() Liefert den Wert, der mit dem aktuellen Thread verbunden ist.
- protected T initialValue() Die Methode kann überschrieben werden und liefert dann den Anfangswert beim ersten Aufruf von get() ohne vorangehendes set(). Der Standard ist null.
- void remove() Entfernt den Wert der Thread-lokalen Variable, um etwa Speicher freizumachen. Ein anschließendes get() liefert wieder den Wert aus initialValue().
Zählen mit thread-lokalen Variablen
Das folgende Beispiel soll ein Runnable definieren, das endlos zählt. Der Clou ist aber, dass der Zähler keine lokale Variable, sondern eine Thread-lokale Variable ist, die ThreadLocal verwaltet. Drei Threads sollen das gleiche Runnable abarbeiten: zwei neue Threads und der aktuell ausführende main-Thread.
Listing 10.30 com/tutego/insel/thread/ThreadLocalDemo.java
package com.tutego.insel.thread; public class ThreadLocalDemo { public static void main( String[] args ) { Runnable runnable = new SimpleRunnable(); new Thread( runnable ).start(); new Thread( runnable ).start(); runnable.run(); } } class SimpleRunnable implements Runnable { private static final ThreadLocal<Integer> mem = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 1; } }; public void run() { while ( true ) { System.out.println( Thread.currentThread().getName() + ", " + mem.get() ); mem.set( mem.get() + 1 ); } } }
Den Startwert legt die überschriebene Methode initialValue() mit 1 fest. Da ThreadLocal einen Zähler speichert, ist der Datencontainer mit Integer typisiert.
Die Ausgabe kann beginnen mit:
main, 1 main, 2 main, 3 main, 4 main, 5 Thread-0, 1 Thread-1, 1 Thread-0, 2 Thread-1, 2 Thread-0, 3 Thread-1, 3
10.8.2 InheritableThreadLocal 

Jeder Thread kann einen neuen Thread bilden, der dann Kind ist. Da die main-Methode selbst von einem Haupt-Thread ausgeführt wird, wie das Beispiel zeigt, ist schon dieser Thread der Vater, der neue Unter-Threads bildet.
Mit der Klasse InheritableThreadLocal, die Unterklasse von ThreadLocal ist, kann das Kind vom Vater den gespeicherten Wert übernehmen. Das würde sonst nicht funktionieren, da der neue Thread ja ganz eigene Werte hat, aber mit InheritableThreadLocal bleibt dieser Wert erhalten und wird auf die Kinder vererbt.
Dass ein gestarteter Kind-Thread den Wert vom Vater-Thread übernimmt, zeigt das folgende Programm. Ein Thread gibt den geerbten Wert aus und setzt anschließend einen neuen Wert für ein Kind, das der Thread in die Welt entlässt.
Listing 10.31 com/tutego/insel/thread/InheritableThreadLocalDemo.java
package com.tutego.insel.thread; public class InheritableThreadLocalDemo { public static void main( String[] args ) { new InheritingThread().start(); } } class InheritingThread extends Thread { // private static final ThreadLocal<String> mem = new ThreadLocal<String>(); private static final InheritableThreadLocal<String> mem = new InheritableThreadLocal<String>(); @Override public void run() { System.out.println( Thread.currentThread() + " bekommt " + mem.get() ); mem.set( Thread.currentThread().getName() ); new InheritingThread().start(); } }
Die Ausgabe beginnt mit:
Thread[Thread-0,5,main] bekommt null Thread[Thread-1,5,main] bekommt Thread-0 Thread[Thread-2,5,main] bekommt Thread-1 Thread[Thread-3,5,main] bekommt Thread-2
Der erste Thread bekommt noch nichts von seinem Vater-Thread. Da jedoch der erste Thread den eigenen Namen in den Speicher von InheritableThreadLocal legt und dann der zweite neu gestartete Kind-Thread auf den Wert zugreift, empfängt er die Zeichenfolge Thread-0.
Wer zum Testen InheritableThreadLocal durch ThreadLocal ersetzt, wird merken, dass das Beispiel so nicht funktioniert.
ThreadLocal bei der Performance-Optimierung
Ein ThreadLocal ist eine schöne Klasse zur Performance-Optimierung. Sollen zum Beispiel zwei Threads große Datenmengen über einen zentralen Puffer lesen, müssten sie an diesem Puffer synchronisiert werden. Mit ThreadLocal kann je ein Puffer beim Thread gespeichert sein, und die Synchronisation kann entfallen. Da es im Allgemeinen wenige Threads gibt, ist die Anzahl paralleler Puffer klein. Performance-Interessierte können einen Blick auf StringCoding werfen, wie sie ThreadLocal beim Caching von (De)kodierer-Objekten nutzt. Die Klasse String nutzt StringCoding bei der Byte/Zeichen-Kodierung. Verschweigen dürfen wir aber nicht, dass der Zugriff auch etwas kostet, so dass ein kleiner synchronisierter Bereich durchaus schneller sein kann.