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?
quelle
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?
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" - daherThread
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 SiethreadLocals = 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 jedemThread
Objekt gespeichert sind (sieheThread.threadLocals
Feld). Die API verbirgt diese Implementierungsdetails, aber das ist mehr oder weniger alles, was dazu gehört.quelle
ThreadLocal-Variablen in Java greifen auf eine HashMap zu, die von der Thread.currentThread () -Instanz gehalten wird.
quelle
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 esthreadLocals
. Da jeder Thread durch eine Thread-Instanz dargestellt wird, wäre dies auchthreadLocals
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 einethreadLocals
für einen Thread haben, haben Sie nur eine für einen bestimmten Thread, wenn Sie sie einfachthreadLocals
als IhreThreadLocal
(z. B. definieren Sie threadLocals alsInteger
) nehmenThreadLocal
. Was ist, wenn Sie mehrereThreadLocal
Variablen für einen Thread möchten ? Der einfachste Weg ist , umthreadLocals
einHashMap
, diekey
von jedem Eintrag ist der Name desThreadLocal
Variable, und dievalue
von jedem Eintrag ist der Wert derThreadLocal
Variablen. Ein bisschen verwirrend? Nehmen wir an, wir haben zwei Threadst1
undt2
. Sie verwenden dieselbeRunnable
Instanz wie der Parameter desThread
Konstruktors und haben beide zweiThreadLocal
Variablen mit dem NamentlA
undtlb
. 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 benutztHashMap
? Um das Problem zu lösen, schauen wir uns an, was passiert, wenn wir einen Wert über dieset(T value)
Methode derThreadLocal
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ückt.threadLocals
. Weilt.threadLocals
wurde initiiertnull
, also geben wircreateMap(t, value)
zuerst ein:void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
Es wird eine neue
ThreadLocalMap
Instanz mit der aktuellenThreadLocal
Instanz und dem einzustellenden Wert erstellt. Mal sehen, wie esThreadLocalMap
ist, es ist tatsächlich Teil derThreadLocal
Klassestatic 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 derEntry class
, der sich erstrecktWeakReference
. Es stellt sicher, dass beim Beenden des aktuellen Threads automatisch Müll gesammelt wird. Aus diesem Grund wirdThreadLocalMap
anstelle eines einfachen verwendetHashMap
. Es übergibt den aktuellenThreadLocal
Wert und seinen Wert als Parameter derEntry
Klasse. Wenn wir also den Wert abrufen möchten, können wir ihn vontable
einer Instanz derEntry
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:
quelle
Konzeptionell kann man sich a
ThreadLocal<T>
als Holding aMap<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
quelle