2009-07-29 5 views

Antwort

84

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.

+1

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

+0

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

+0

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

7

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

+0

, 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

+1

Das ist im Grunde, was ich gesagt habe. currentThread() gibt eine Thread-Instanz zurück, die eine Map von ThreadLocals auf Werte enthält. –

+0

Es ist keine Karte! – user924272

29

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.

+0

Also, es entstehen keine Sperren oder Konflikte, oder? – ripper234

+0

Ich kann nicht sehen, warum irgendwelche benötigt werden, da die Daten per Definition nur für einen einzigen Thread sichtbar sind. – skaffman

+7

Korrekt, es gibt keine Synchronisierung oder Sperrung um oder innerhalb der ThreadLocalMap, da nur auf In-Thread zugegriffen wird. – Cowan

1

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:

The Whole Picture

0

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

+0

Konzeptuell, ja. Aber wie Sie aus anderen Antworten sehen, ist die Implementierung völlig anders. – Archit