Wie ist ThreadLocal implementiert? Ist es in Java implementiert (unter Verwendung einer gleichzeitigen Zuordnung von ThreadID zu Objekt) oder verwendet es einen JVM-Hook, um es effizienter zu machen?
Antworten:
Alle Antworten hier sind richtig, aber ein wenig enttäuschend, da sie etwas beschönigen, wie clever ThreadLocal
die Implementierung ist. Ich habe mir nur den Quellcode angesehenThreadLocal
und war angenehm beeindruckt von der Implementierung.
Die naive Implementierung
ThreadLocal<T>
Was würden Sie tun, wenn ich Sie bitten würde, eine Klasse mit der im Javadoc beschriebenen API zu implementieren ? Eine erste Implementierung wäre wahrscheinlich eine ConcurrentHashMap<Thread,T>
Verwendung Thread.currentThread()
als Schlüssel. Dies würde einigermaßen gut funktionieren, hat jedoch einige Nachteile.
ConcurrentHashMap
ist eine ziemlich kluge Klasse, aber sie muss sich letztendlich immer noch damit befassen, zu verhindern, dass mehrere Threads in irgendeiner Weise damit herumspielen, und wenn verschiedene Threads sie regelmäßig treffen, kommt es zu Verlangsamungen.Die GC-freundliche Implementierung
Ok, versuchen Sie es noch einmal. Lassen Sie uns das Problem der Speicherbereinigung mithilfe schwacher Referenzen lösen . Der Umgang mit WeakReferences kann verwirrend sein, sollte jedoch ausreichen, um eine Karte zu verwenden, die wie folgt erstellt wurde:
Collections.synchronizedMap(new WeakHashMap<Thread, T>())
Oder wenn wir Guave benutzen (und wir sollten es sein!):
new MapMaker().weakKeys().makeMap()
Dies bedeutet, dass, sobald niemand anderes am Thread festhält (was bedeutet, dass er fertig ist), der Schlüssel / Wert durch Müll gesammelt werden kann. Dies ist eine Verbesserung, behebt jedoch immer noch nicht das Problem mit den Thread-Konflikten, was bedeutet, dass dies bisher ThreadLocal
nicht alles ist erstaunlich von einer Klasse. Wenn sich jemand dazu entschließen würde, an Thread
Objekten festzuhalten , nachdem er fertig war, würde er niemals GC'ed werden, und daher würden unsere Objekte auch nicht, obwohl sie jetzt technisch nicht erreichbar sind.
Die clevere Implementierung
Wir haben darüber nachgedacht, ThreadLocal
Threads Werten zuzuordnen, aber vielleicht ist das nicht der richtige Weg, darüber nachzudenken. Anstatt es als Zuordnung von Threads zu Werten in jedem ThreadLocal-Objekt zu betrachten, was wäre, wenn wir es als Zuordnung von ThreadLocal-Objekten zu Werten in jedem Thread betrachten würden ? Wenn jeder Thread das Mapping speichert und ThreadLocal lediglich eine nette Schnittstelle zu diesem Mapping bietet, können wir alle Probleme der vorherigen Implementierungen vermeiden.
Eine Implementierung würde ungefähr so aussehen:
// called for each thread, and updated by the ThreadLocal instance
new WeakHashMap<ThreadLocal,T>()
Hier besteht kein Grund zur Sorge, da nur ein Thread jemals auf diese Karte zugreifen wird.
Die Java-Entwickler haben hier einen großen Vorteil gegenüber uns - sie können die Thread-Klasse direkt entwickeln und Felder und Operationen hinzufügen, und genau das haben sie getan.
In java.lang.Thread
gibt es die folgenden Zeilen:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Was, wie der Kommentar andeutet, tatsächlich eine paketprivate Zuordnung aller Werte ist, die von ThreadLocal
Objekten dafür verfolgt werden Thread
. Die Implementierung von ThreadLocalMap
ist kein WeakHashMap
, aber es folgt dem gleichen Grundvertrag, einschließlich des Haltens seiner Schlüssel durch schwache Referenz.
ThreadLocal.get()
wird dann wie folgt implementiert:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
Und ThreadLocal.setInitialValue()
so:
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
Verwenden Sie im Wesentlichen eine Karte in diesem Thread , um alle unsere ThreadLocal
Objekte aufzunehmen. Auf diese Weise müssen wir uns nie um die Werte in anderen Threads kümmern (können ThreadLocal
buchstäblich nur auf die Werte im aktuellen Thread zugreifen) und haben daher keine Probleme mit der Parallelität. Sobald Thread
dies erledigt ist, wird die Karte automatisch GC-geprüft und alle lokalen Objekte werden bereinigt. Selbst wenn das Objekt Thread
festgehalten wird, werden die ThreadLocal
Objekte durch schwache Referenz gehalten und können bereinigt werden, sobald das ThreadLocal
Objekt den Gültigkeitsbereich verlässt.
Unnötig zu erwähnen, dass ich von dieser Implementierung ziemlich beeindruckt war. Sie umgeht auf elegante Weise viele Probleme mit der Parallelität (zwar durch die Nutzung des Java-Kerns, aber das ist verzeihlich, da es sich um eine so clevere Klasse handelt) und ermöglicht schnell und schnell Thread-sicherer Zugriff auf Objekte, auf die jeweils nur ein Thread zugreifen muss.
Die ThreadLocal
Implementierung von tl; dr ist ziemlich cool und viel schneller / intelligenter als Sie vielleicht auf den ersten Blick denken.
Wenn Ihnen diese Antwort gefallen hat, werden Sie vielleicht auch meine (weniger detaillierte) Diskussion überThreadLocalRandom
schätzen .
Thread
/ ThreadLocal
Code-Schnipsel aus der Implementierung von Java 8 durch Oracle / OpenJDK .
WeakHashMap<String,T>
führt mehrere Probleme ein, ist nicht threadsicher und "hauptsächlich für die Verwendung mit Schlüsselobjekten vorgesehen, deren Methoden mit dem Operator == auf Objektidentität getestet werden" - daher Thread
wäre es besser , das Objekt tatsächlich als Schlüssel zu verwenden. Ich würde vorschlagen, die oben beschriebene Guava schwachKeys-Karte für Ihren Anwendungsfall zu verwenden.
Thread.exit()
wird, aufgerufen wird und Sie threadLocals = null;
genau dort sehen werden. Ein Kommentar verweist auf diesen Fehler, den Sie möglicherweise auch gerne lesen.
Du meinst java.lang.ThreadLocal
. Eigentlich ist es ganz einfach, es ist nur eine Karte von Name-Wert-Paaren, die in jedem Thread
Objekt gespeichert sind (siehe Thread.threadLocals
Feld). Die API verbirgt diese Implementierungsdetails, aber das ist mehr oder weniger alles, was dazu gehört.
ThreadLocal-Variablen in Java greifen auf eine HashMap zu, die von der Thread.currentThread () -Instanz gehalten wird.
Angenommen, Sie werden implementieren ThreadLocal
. Wie machen Sie es threadspezifisch? Die einfachste Methode besteht natürlich darin, ein nicht statisches Feld in der Thread-Klasse zu erstellen. Nennen wir es threadLocals
. Da jeder Thread durch eine Thread-Instanz dargestellt wird, wäre dies auch threadLocals
in jedem Thread anders. Und das macht Java auch:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Was ist ThreadLocal.ThreadLocalMap
hier Da Sie nur eine threadLocals
für einen Thread haben, haben Sie nur eine für einen bestimmten Thread, wenn Sie sie einfach threadLocals
als Ihre ThreadLocal
(z. B. definieren Sie threadLocals als Integer
) nehmen ThreadLocal
. Was ist, wenn Sie mehrere ThreadLocal
Variablen für einen Thread möchten ? Der einfachste Weg ist , um threadLocals
ein HashMap
, die key
von jedem Eintrag ist der Name des ThreadLocal
Variable, und die value
von jedem Eintrag ist der Wert der ThreadLocal
Variablen. Ein bisschen verwirrend? Nehmen wir an, wir haben zwei Threads t1
und t2
. Sie verwenden dieselbe Runnable
Instanz wie der Parameter des Thread
Konstruktors und haben beide zwei ThreadLocal
Variablen mit dem Namen tlA
und tlb
. So ist es.
t1.tlA
+-----+-------+
| Key | Value |
+-----+-------+
| tlA | 0 |
| tlB | 1 |
+-----+-------+
t2.tlB
+-----+-------+
| Key | Value |
+-----+-------+
| tlA | 2 |
| tlB | 3 |
+-----+-------+
Beachten Sie, dass die Werte von mir gemacht werden.
Jetzt scheint es perfekt zu sein. Aber was ist das ThreadLocal.ThreadLocalMap
? Warum wurde es nicht einfach benutzt HashMap
? Um das Problem zu lösen, schauen wir uns an, was passiert, wenn wir einen Wert über die set(T value)
Methode der ThreadLocal
Klasse festlegen :
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
getMap(t)
kehrt einfach zurück t.threadLocals
. Weil t.threadLocals
wurde initiiert null
, also geben wir createMap(t, value)
zuerst ein:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Es wird eine neue ThreadLocalMap
Instanz mit der aktuellen ThreadLocal
Instanz und dem einzustellenden Wert erstellt. Mal sehen, wie es ThreadLocalMap
ist, es ist tatsächlich Teil der ThreadLocal
Klasse
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
...
}
Der Kern der ThreadLocalMap
Klasse ist der Entry class
, der sich erstreckt WeakReference
. Es stellt sicher, dass beim Beenden des aktuellen Threads automatisch Müll gesammelt wird. Aus diesem Grund wird ThreadLocalMap
anstelle eines einfachen verwendet HashMap
. Es übergibt den aktuellen ThreadLocal
Wert und seinen Wert als Parameter der Entry
Klasse. Wenn wir also den Wert abrufen möchten, können wir ihn von table
einer Instanz der Entry
Klasse abrufen:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
So sieht das ganze Bild aus:
Konzeptionell kann man sich a ThreadLocal<T>
als Holding a Map<Thread,T>
vorstellen, in dem die threadspezifischen Werte gespeichert sind, obwohl dies nicht der Fall ist.
Die threadspezifischen Werte werden im Thread-Objekt selbst gespeichert. Wenn der Thread beendet wird, können die threadspezifischen Werte durch Müll gesammelt werden.
Referenz: JCIP