Wie wird Javas ThreadLocal unter der Haube implementiert?

Lesezeit: 10 Minuten

Benutzer-Avatar
Reißer234

Wie wird 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?

Benutzer-Avatar
dimo414

Alle Antworten hier sind richtig, aber ein wenig enttäuschend, da sie etwas darüber hinwegtäuschen, wie klug sie sind ThreadLocalDie Implementierung von ist. Ich habe mir gerade die angeschaut Quellcode für ThreadLocal und war angenehm beeindruckt von der Umsetzung.

Die naive Implementierung

Wenn ich Sie bitten würde, a ThreadLocal<T> Klasse angesichts der im Javadoc beschriebenen API, was würden Sie tun? Eine erste Implementierung wäre wahrscheinlich a ConcurrentHashMap<Thread,T> verwenden Thread.currentThread() als seinen Schlüssel. Dies würde ziemlich gut funktionieren, hat aber einige Nachteile.

  • Thread-Konflikt – ConcurrentHashMap ist eine ziemlich schlaue Klasse, aber sie muss letztendlich immer noch verhindern, dass mehrere Threads in irgendeiner Weise damit herumspielen, und wenn verschiedene Threads sie regelmäßig treffen, wird es zu Verlangsamungen kommen.
  • Behält dauerhaft einen Zeiger sowohl auf den Thread als auch auf das Objekt, selbst nachdem der Thread beendet wurde und GC’ed werden könnte.

Die GC-freundliche Implementierung

Ok, versuchen Sie es noch einmal. Lassen Sie uns das Problem der Garbage Collection mithilfe von lösen schwache Referenzen. Der Umgang mit WeakReferences kann verwirrend sein, aber es sollte ausreichen, eine Karte wie folgt zu verwenden:

 Collections.synchronizedMap(new WeakHashMap<Thread, T>())

Oder wenn wir verwenden Guave (und wir sollten es sein!):

new MapMaker().weakKeys().makeMap()

Das bedeutet, sobald niemand mehr an dem Thread festhält (was bedeutet, dass er fertig ist), kann der Schlüssel/Wert der Garbage Collection unterzogen werden, was eine Verbesserung darstellt, aber immer noch nicht das Problem der Thread-Konkurrenz anspricht, was bisher unser bedeutet ThreadLocal ist nicht so erstaunlich von einer Klasse. Außerdem, wenn sich jemand zum Festhalten entschließt Thread Objekte, nachdem sie fertig waren, würden sie niemals GC’ed werden, und daher auch unsere Objekte nicht, obwohl sie jetzt technisch nicht erreichbar sind.

Die clevere Umsetzung

Wir haben darüber nachgedacht ThreadLocal als Zuordnung von Threads zu Werten, aber vielleicht ist das nicht die richtige Art, darüber nachzudenken. Anstatt es als eine Zuordnung von Threads zu Werten in jedem ThreadLocal-Objekt zu betrachten, was wäre, wenn wir es als eine Zuordnung von ThreadLocal-Objekten zu Werten betrachten würden in jedem Thread? Wenn jeder Thread die Zuordnung speichert und ThreadLocal lediglich eine nette Schnittstelle zu dieser Zuordnung bereitstellt, können wir alle Probleme der vorherigen Implementierungen vermeiden.

Eine Implementierung würde in etwa so aussehen:

// called for each thread, and updated by the ThreadLocal instance
new WeakHashMap<ThreadLocal,T>()

Sie müssen sich hier keine Gedanken über Parallelität machen, da immer nur ein Thread auf diese Map zugreift.

Die Java-Entwickler haben hier einen großen Vorteil gegenüber uns – sie können die Thread-Klasse direkt entwickeln und ihr Felder und Operationen hinzufügen, und genau das haben sie getan.

Im java.lang.Thread da sind folgende zeilen:

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

Wie der Kommentar andeutet, handelt es sich tatsächlich um eine paketprivate Zuordnung aller Werte, die verfolgt werden ThreadLocal Objekte dazu Thread. Die Implementierung von ThreadLocalMap ist kein WeakHashMapaber es folgt dem gleichen Grundvertrag, einschließlich des Haltens seiner Schlüssel durch schwache Referenz.

ThreadLocal.get() wird dann so implementiert:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap
    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
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

Verwenden Sie im Wesentlichen eine Karte in diesem Thread alle unsere zu halten 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 Parallelitätsprobleme. Außerdem einmal die Thread fertig ist, wird seine Karte automatisch mit GC versehen und alle lokalen Objekte werden bereinigt. Auch wenn die Thread festgehalten wird, die ThreadLocal Objekte werden durch schwache Referenz gehalten und können aufgeräumt werden, sobald die ThreadLocal Objekt verlässt den Gültigkeitsbereich.


Unnötig zu sagen, dass ich von dieser Implementierung ziemlich beeindruckt war, sie umgeht ziemlich elegant viele Nebenläufigkeitsprobleme (zugegebenermaßen, indem sie den Vorteil ausnutzt, Teil von Kern-Java zu sein, aber das ist verzeihlich, da es eine so clevere Klasse ist) und ermöglicht schnelles und Thread-sicherer Zugriff auf Objekte, auf die jeweils nur ein Thread zugreifen muss.

tl;dr ThreadLocalDie Implementierung von ist ziemlich cool und viel schneller/intelligenter, als Sie auf den ersten Blick denken.

Wenn Ihnen diese Antwort gefallen hat, wissen Sie vielleicht auch meine (weniger detaillierte) Diskussion von zu schätzen ThreadLocalRandom.

Thread/ThreadLocal Codeschnipsel entnommen aus Oracle/OpenJDKs Implementierung von Java 8.

  • Deine Antworten sehen toll aus, aber das ist mir jetzt zu lang. +1 und akzeptiert, und ich füge es meinem getpocket.com-Konto hinzu, um es später zu lesen. Vielen Dank!

    – Reißer234

    27. März 2013 um 11:59 Uhr

  • Ich brauche ein ThreadLocal-ähnliches Ding, mit dem ich auch auf die vollständige Liste der Werte zugreifen kann, ähnlich wie map.values(). Meine naive Implementierung ist also eine WeakHashMap, wobei der Schlüssel Thread.currentThread().getName() ist. Dadurch wird der Verweis auf den Thread selbst vermieden. Wenn der Thread verschwindet, dann hält nichts mehr den Namen des Threads (eine Annahme, gebe ich zu) und mein Wert wird verschwinden.

    – bmauter

    28. März 2013 um 14:50 Uhr

  • Nun, die schwachen Schlüssel sind nicht notwendig, aber erwägen Sie die Verwendung einer ConcurrentHashMap über einer synchronisierten HashMap – erstere ist für den Multithread-Zugriff konzipiert und funktioniert viel besser, wenn jeder Thread im Allgemeinen auf einen anderen Schlüssel zugreift.

    – dimo414

    28. März 2013 um 20:27 Uhr

  • @shmosel danke, froh, dass es informativ war! Ich glaube, es liegt daran ThreadLocalDie Anforderungen von unterscheiden sich geringfügig von denen von a WeakHashMap. Zum Beispiel verwendet es a benutzerdefinierte Hash-Funktion “* das eliminiert Kollisionen im allgemeinen Fall, in dem nacheinander erstellte ThreadLocals von denselben Threads verwendet werden *”, und bietet unterschiedliche Bereinigungssemantik.

    – dimo414

    12. Mai 2016 um 5:29 Uhr

  • @shmosel diese Klasse ist höchst abgestimmt, also würde ich davon ausgehen, dass solche Überlegungen angestellt wurden. Ein kurzer Blick zeigt das, wenn ein Thread normal beendet wird Thread.exit() wird aufgerufen und du wirst sehen threadLocals = null; genau da. Ein Kommentar verweist dieser Fehler die Sie vielleicht auch gerne lesen.

    – dimo414

    12. Mai 2016 um 5:46 Uhr

Was meinen Sie java.lang.ThreadLocal. Es ist eigentlich ganz einfach, es ist nur eine Karte von Name-Wert-Paaren, die in jedem gespeichert sind Thread Objekt (siehe die Thread.threadLocals aufstellen). Die API verbirgt dieses Implementierungsdetail, aber das ist mehr oder weniger alles, was dazu gehört.

  • Ich kann nicht sehen, warum welche benötigt werden sollten, da die Daten per Definition nur für einen einzelnen Thread sichtbar sind.

    – Skaffmann

    30. Juli 2009 um 7:14 Uhr

  • Richtig, es gibt keine Synchronisierung oder Sperrung um oder innerhalb der ThreadLocalMap, da nur im Thread darauf zugegriffen wird.

    – Cowan

    30. Juli 2009 um 7:31 Uhr

Benutzer-Avatar
Chris Vest

ThreadLocal-Variablen in Java funktionieren durch den Zugriff auf eine HashMap, die von der Thread.currentThread()-Instanz gehalten wird.

  • Das ist nicht richtig (oder zumindest nicht mehr). Thread.currentThread() ist ein nativer Aufruf in der Thread.class. Außerdem hat Thread eine “ThreadLocalMap”, die ein einzelner Bucket (Array) von Hash-Codes ist. Dieses Objekt unterstützt die Map-Schnittstelle nicht.

    – Benutzer924272

    24. Juni 2014 um 16:59 Uhr


  • Das habe ich im Grunde gesagt. currentThread() gibt eine Thread-Instanz zurück, die eine Zuordnung von ThreadLocals zu Werten enthält.

    – Chris Vest

    25. Juni 2014 um 22:28 Uhr

Benutzer-Avatar
Searene

Angenommen, Sie werden implementieren ThreadLocal, wie machen Sie es Thread-spezifisch? 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 Threadinstanz repräsentiert wird, also threadLocals in jedem Thread wäre das auch 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? Denn du hast nur eine threadLocals für einen Thread, also wenn du einfach nimmst threadLocals wie dein ThreadLocal(z. B. definiere threadLocals als Integer), haben Sie nur eine ThreadLocal für einen bestimmten Faden. Was ist, wenn Sie mehrere wollen ThreadLocal Variablen für einen Thread? Der einfachste Weg ist zu machen threadLocals a HashMapdas key von jedem Eintrag ist der Name des ThreadLocal Variable, und die value jedes Eintrags ist der Wert der ThreadLocal Variable. Etwas verwirrend? Sagen wir, wir haben zwei Threads, t1 und t2. sie nehmen das gleiche Runnable Instanz als Parameter von Thread Konstruktor, und beide haben zwei ThreadLocal Variablen benannt 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 erstellt wurden.

Jetzt scheint es perfekt. Aber was ist ThreadLocal.ThreadLocalMap? Warum hat es nicht einfach verwendet HashMap? Um das Problem zu lösen, sehen wir uns an, was passiert, wenn wir einen Wert über die setzen set(T value) Methode der ThreadLocal Klasse:

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

getMap

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

Es schafft ein neues ThreadLocalMap Instanz mit dem Strom ThreadLocal Instanz und den einzustellenden Wert. Mal sehen, was ThreadLocalMap ist wie, es ist in der Tat 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 classdie sich erstreckt WeakReference. Es stellt sicher, dass beim Beenden des aktuellen Threads automatisch eine Garbage Collection durchgeführt wird. Aus diesem Grund verwendet es ThreadLocalMap statt einfach HashMap. Es leitet den Strom ThreadLocal und seinen Wert als Parameter der Entry Klasse, also wenn wir den Wert bekommen wollen, könnten wir ihn bekommen tabledie eine Instanz von ist Entry Klasse:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap
    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:

Das ganze Bild

Konzeptionell können Sie an a denken ThreadLocal<T> als Halten von a Map<Thread,T> das die Thread-spezifischen Werte speichert, obwohl es nicht so implementiert ist.

Die Thread-spezifischen Werte werden im Thread-Objekt selbst gespeichert; Wenn der Thread beendet wird, können die Thread-spezifischen Werte einer Garbage Collection unterzogen werden.

Bezug : JCIP

  • Konzeptionell ja. Aber wie Sie aus anderen Antworten oben sehen, ist die Implementierung völlig anders.

    – Architekt

    2. Januar 2018 um 7:55 Uhr

  • Konzeptionell ja. Aber wie Sie aus anderen Antworten oben sehen, ist die Implementierung völlig anders.

    – Architekt

    2. Januar 2018 um 7:55 Uhr

1229390cookie-checkWie wird Javas ThreadLocal unter der Haube implementiert?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy