Wie ist ThreadLocal implementiert? Ist es in Java implementiert (mit einer gleichzeitigen Zuordnung von ThreadID zu Objekt), oder nutzt es einen JVM-Hook, um es effizienter zu machen?Wie wird Javas ThreadLocal unter der Haube implementiert?
Antwort
Alle Antworten hier sind korrekt, aber ein wenig enttäuschend, da sie etwas glänzen über, wie klug ThreadLocal
Implementierung ist. Ich schaute nur auf die source code for ThreadLocal
und war angenehm beeindruckt, wie es umgesetzt wird.
Die Naive Implementierung
Wenn ich Sie gebeten, eine ThreadLocal<T>
Klasse angesichts der API in der javadoc beschrieben zu implementieren, was würden Sie tun? Eine erste Implementierung wäre wahrscheinlich eine ConcurrentHashMap<Thread,T>
, die Thread.currentThread()
als ihren Schlüssel verwendet. Dieser Wille würde einigermaßen gut funktionieren, hat aber einige Nachteile.
- Thread-Konkurrenz -
ConcurrentHashMap
ist eine ziemlich intelligente Klasse, aber es letztlich noch mit ihm in irgendeiner Weise mit der Verhütung von mehreren Threads von Ausmisten zu behandeln, und wenn verschiedene Threads es regelmäßig treffen, wird es Verlangsamungen sein. - Bleibt permanent ein Zeiger auf den Thread und das Objekt, auch nachdem der Thread beendet wurde und GC'ed werden konnte.
Die GC-freundliche Umsetzung
Ok versuchen erneut, können befassen sich mit der Sammlung der Ausgabe Müll von weak references verwenden. Der Umgang mit WeakReferences kann verwirrend sein, aber es sollte eine Karte verwenden gebaut wie so ausreichend sein: (! Und wir sollten sein)
Collections.synchronizedMap(new WeakHashMap<Thread, T>())
Oder wenn wir verwenden Guava:
new MapMaker().weakKeys().makeMap()
Diese bedeutet, wenn niemand sonst den Thread festhält (was bedeutet, dass er beendet ist), kann der Schlüssel/Wert eine Garbage Collection sein, was eine Verbesserung darstellt, aber immer noch nicht das Thread Contention Problem angeht, was bisher ThreadLocal
ist erstaunlich einer Klasse. Außerdem, wenn jemand beschlossen hat, Thread
Objekte zu behalten, nachdem sie beendet wurden, würden sie niemals GC'ed werden, und deshalb würden unsere Objekte auch nicht, obwohl sie technisch jetzt unerreichbar sind.
Die clevere Implementierung
Wir haben über ThreadLocal
als eine Abbildung von Threads auf Werte gedacht, aber das ist vielleicht nicht wirklich der richtige Weg, um darüber nachzudenken. Anstatt es als eine Zuordnung von Threads zu Werten in jedem ThreadLocal-Objekt zu betrachten, was ist, wenn wir darüber nachdenken, wie sich ThreadLocal-Objekte auf Werte in jedem Thread beziehen?Wenn jeder Thread das Mapping speichert und ThreadLocal lediglich eine nette Schnittstelle für dieses Mapping bereitstellt, können wir alle Probleme der vorherigen Implementierungen vermeiden.
Eine Implementierung würde wie folgt aussehen:
// called for each thread, and updated by the ThreadLocal instance
new WeakHashMap<ThreadLocal,T>()
Es gibt keine Notwendigkeit, über Gleichzeitigkeit hier zu kümmern, weil nur ein Thread jemals diese Karte wird erreichbar.
Die Java-Entwickler haben hier einen großen Vorteil - 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;
, die als Kommentar schlägt vor, ist in der Tat ein Paket-private Abbildung aller Werte von ThreadLocal
Objekte für dieses Thread
verfolgt. Die Implementierung von ThreadLocalMap
ist kein WeakHashMap
, aber es folgt der gleiche Grundvertrag, einschließlich das Halten seiner Schlüssel durch schwache Referenz.
ThreadLocal.get()
wird dann wie so umgesetzt:
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()
wie 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; }
Im Wesentlichen verwenden, um eine Karte in diesem Thread zu halten alle unsere ThreadLocal
Objekte. Auf diese Weise müssen wir uns nie um die Werte in anderen Threads kümmern (ThreadLocal
können buchstäblich nur auf die Werte im aktuellen Thread zugreifen) und haben daher keine Probleme mit dem gemeinsamen Zugriff. Sobald die Thread
fertig ist, wird ihre Map automatisch gecodiert und alle lokalen Objekte werden bereinigt. Selbst wenn die Thread
gehalten wird, werden die ThreadLocal
Objekte durch schwache Referenz gehalten und können aufgeräumt werden, sobald das Objekt ThreadLocal
den Geltungsbereich verlässt.
Unnötig zu sagen, ich war ziemlich beeindruckt von dieser Implementierung ist es ziemlich bekommt elegant um eine Menge von Concurrency Probleme (zugegebenermaßen von Vorteil Teilnahme von Kern Java zu sein, aber das ist sie verzeihlich, da es ein kluger so ist Klasse) und ermöglicht einen schnellen und Thread-sicheren Zugriff auf Objekte, auf die jeweils nur ein Thread zugreifen muss.
tl; drThreadLocal
's Implementierung ist ziemlich cool, und viel schneller/schlauer als Sie vielleicht auf den ersten Blick denken.
Wenn Ihnen diese Antwort gefallen hat, könnten Sie auch meine (weniger detaillierte) discussion of ThreadLocalRandom
schätzen.
Thread
/ThreadLocal
Code-Schnipsel aus Oracle/OpenJDK's implementation of Java 8 genommen.
ThreadLocal-Variablen in Java funktioniert durch Zugriff auf eine HashMap, die von der Thread.currentThread() - Instanz gehalten wird.
, die nicht korrekt ist (oder zumindest ist es nicht mehr). Thread.currentThread() ist ein systemeigener Aufruf in der Thread.class. Auch Thread hat eine "ThreadLocalMap", die ein Single-Bucket (Array) von Hash-Codes ist. Dieses Objekt unterstützt die Kartenschnittstelle nicht. – user924272
Das ist im Grunde, was ich gesagt habe. currentThread() gibt eine Thread-Instanz zurück, die eine Map von ThreadLocals auf Werte enthält. –
Es ist keine Karte! – user924272
Sie meinen java.lang.ThreadLocal
. Es ist ganz einfach, wirklich, es ist nur eine Karte von Name-Wert-Paaren, die in jedem Thread
Objekt gespeichert sind (siehe Feld Thread.threadLocals
). Die API versteckt dieses Implementierungsdetail, aber das ist mehr oder weniger alles, was dazu gehört.
Also, es entstehen keine Sperren oder Konflikte, oder? – ripper234
Ich kann nicht sehen, warum irgendwelche benötigt werden, da die Daten per Definition nur für einen einzigen Thread sichtbar sind. – skaffman
Korrekt, es gibt keine Synchronisierung oder Sperrung um oder innerhalb der ThreadLocalMap, da nur auf In-Thread zugegriffen wird. – Cowan
Angenommen, Sie implementieren ThreadLocal
, wie machen Sie es Thread-spezifisch? Natürlich ist die einfachste Methode, ein nicht-statisches Feld in der Thread-Klasse zu erstellen, nennen wir es threadLocals
. Da jeder Thread durch eine Thread-Instanz repräsentiert wird, wäre auch threadLocals
in jedem Thread anders. Und das ist auch das, was Java tut:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Was ist ThreadLocal.ThreadLocalMap
hier? Weil Sie nur eine threadLocals
für einen Thread haben, also wenn Sie einfach threadLocals
als Ihre ThreadLocal
nehmen (sagen wir, threadLocals als Integer
), haben Sie nur eine ThreadLocal
für einen bestimmten Thread. Was ist, wenn Sie mehrere ThreadLocal
Variablen für einen Thread möchten? Der einfachste Weg ist threadLocals
ein HashMap
, der key
jedes Eintrags ist der Name der ThreadLocal
Variable, und der value
von jedem Eintrag ist der Wert der ThreadLocal
Variable. Ein bisschen verwirrend? Nehmen wir an, wir haben zwei Threads, t1
und t2
. Sie nehmen die gleiche Runnable
Instanz als den Parameter Thread
Konstruktor, und sie haben beide zwei ThreadLocal
Variablen mit den 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. Aber was ist ThreadLocal.ThreadLocalMap
? Warum hat es nicht einfach HashMap
verwendet? Um das Problem zu lösen, wollen wir sehen, was passiert, wenn wir einen Wert durch die set(T value)
Methode der ThreadLocal
Klasse festgelegt:
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)
gibt einfach t.threadLocals
. Da t.threadLocals
zu null
initilized wurde, so geben wir createMap(t, value)
zuerst:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Es schafft eine neue ThreadLocalMap
Instanz die aktuelle ThreadLocal
Instanz und den Wert mit eingestellt werden. Mal sehen, was ThreadLocalMap
ist wie, es ist in der Tat ein 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 Kernteil der ThreadLocalMap
Klasse ist die Entry class
, die WeakReference
erstreckt. Es stellt sicher, dass wenn der aktuelle Thread beendet wird, automatisch Müll gesammelt wird. Aus diesem Grund verwendet es ThreadLocalMap
statt einer einfachen HashMap
.Er übergibt den aktuellen ThreadLocal
und seinen Wert als Parameter der Entry
Klasse, so dass, wenn wir den Wert erhalten möchten, könnten wir es aus table
, erhalten, die eine Instanz der Entry
Klasse:
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();
}
Dies ist was ist wie im ganzen Bild:
Konzeptionell, können Sie eine ThreadLocal<T>
denken Sie an einen Map<Thread,T>
als holding, die die faden spezi fi schen Werte speichert, obwohl dies nicht der Fall ist, wie es tatsächlich ist implementiert.
Die Thread-spezifischen Werte werden im Thread-Objekt selbst gespeichert. Wenn der Thread beendet wird, können die threadspezifischen Werte als Garbage Collected erfasst werden.
Referenz: JCIP
Konzeptuell, ja. Aber wie Sie aus anderen Antworten sehen, ist die Implementierung völlig anders. – Archit
Ihre Antworten sehen fantastisch aus, aber im Moment ist es zu lang für mich. +1 und akzeptiert, und ich füge es meinem getpocket.com-Konto hinzu, um es später zu lesen. Vielen Dank! – ripper234
Ich brauche ein ThreadLocal-ähnliches Ding, das mir auch den Zugriff auf die vollständige Liste der Werte ermöglicht, ähnlich wie map.values (). Also, meine naive Implementierung ist eine WeakHashMap wobei der Schlüssel Thread.currentThread(). GetName() ist. Dies vermeidet den Verweis auf den Thread selbst. Wenn der Thread verschwindet, dann hält nichts mehr den Namen des Threads (eine Annahme, ich gebe es zu) und mein Wert wird weggehen. –
bmauter
Ich habe [tatsächlich eine Frage in letzter Zeit beantwortet] (http://stackoverflow.com/a/15654081/113632). Ein 'WeakHashMap' führt zu mehreren Problemen, es ist nicht threadsicher, und es "ist in erster Linie für die Verwendung mit Schlüsselobjekten gedacht, deren equals-Methoden die Objektidentität mit dem Operator == testen - also das Thread-Objekt als Schlüssel verwenden würde Machs besser. Ich würde vorschlagen, die Guava weapKeys Map, die ich oben für Ihren Anwendungsfall beschrieben habe, zu verwenden. –
dimo414