Jak zaimplementowano ThreadLocal? Czy jest zaimplementowany w Javie (przy użyciu pewnej współbieżnej mapy z ThreadID do obiektu), czy może używa jakiegoś haka JVM, aby zrobić to bardziej wydajnie?
źródło
Jak zaimplementowano ThreadLocal? Czy jest zaimplementowany w Javie (przy użyciu pewnej współbieżnej mapy z ThreadID do obiektu), czy może używa jakiegoś haka JVM, aby zrobić to bardziej wydajnie?
Wszystkie odpowiedzi tutaj są poprawne, ale trochę rozczarowujące, ponieważ nieco przemilczają, jak sprytna ThreadLocal
jest implementacja. Właśnie patrzyłem na kod źródłowy programuThreadLocal
i byłem pod wrażeniem sposobu jego implementacji.
Naiwna implementacja
ThreadLocal<T>
Co byś zrobił, gdybym poprosił Cię o zaimplementowanie klasy posiadającej API opisane w javadoc? Początkowa implementacja prawdopodobnie będzie kluczem do ConcurrentHashMap<Thread,T>
użycia Thread.currentThread()
. To działałoby dość dobrze, ale ma pewne wady.
ConcurrentHashMap
to całkiem sprytna klasa, ale ostatecznie nadal musi radzić sobie z zapobieganiem w jakikolwiek sposób wielu wątków, a jeśli różne wątki będą go regularnie uderzać, wystąpią spowolnienia.Implementacja przyjazna dla GC
Ok, spróbuj ponownie, zajmijmy się problemem czyszczenia pamięci, używając słabych referencji . Radzenie sobie z WeakReferences może być mylące, ale powinno wystarczyć użycie mapy zbudowanej w ten sposób:
Collections.synchronizedMap(new WeakHashMap<Thread, T>())
Lub jeśli używamy guawy (a powinniśmy!):
new MapMaker().weakKeys().makeMap()
Oznacza to, że gdy nikt inny nie trzyma wątku (co oznacza, że jest skończony), klucz / wartość może zostać zebrany jako śmieci, co jest ulepszeniem, ale nadal nie rozwiązuje problemu rywalizacji o wątek, co oznacza, że jak dotąd nasze ThreadLocal
nie wszystko niesamowita klasa. Co więcej, jeśli ktoś zdecydowałby się trzymać Thread
przedmioty po ich zakończeniu, nigdy nie otrzymałyby one GC, a zatem również naszych obiektów, mimo że są one obecnie technicznie nieosiągalne.
Sprytna implementacja
Myśleliśmy o ThreadLocal
mapowaniu wątków na wartości, ale może to nie jest właściwy sposób myślenia o tym. Zamiast myśleć o tym jako o mapowaniu ThreadLocal na wartości w każdym obiekcie ThreadLocal, co by było, gdybyśmy pomyśleli o tym jako o mapowaniu obiektów ThreadLocal na wartości w każdym ThreadLocal ? Jeśli każdy wątek przechowuje mapowanie, a ThreadLocal po prostu zapewnia ładny interfejs do tego mapowania, możemy uniknąć wszystkich problemów z poprzednimi implementacjami.
Implementacja wyglądałaby mniej więcej tak:
// called for each thread, and updated by the ThreadLocal instance
new WeakHashMap<ThreadLocal,T>()
Nie musisz się tutaj martwić o współbieżność, ponieważ tylko jeden wątek będzie miał kiedykolwiek dostęp do tej mapy.
Programiści Java mają tutaj nad nami dużą przewagę - mogą bezpośrednio rozwijać klasę Thread i dodawać do niej pola i operacje, i właśnie to zrobili.
W java.lang.Thread
środku znajdują się następujące wiersze:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Co, jak sugeruje komentarz, jest rzeczywiście prywatnym mapowaniem pakietów wszystkich wartości śledzonych przez ThreadLocal
obiekty w tym celu Thread
. Implementacja ThreadLocalMap
nie jest WeakHashMap
, ale przebiega zgodnie z tą samą podstawową umową, w tym trzymaniem kluczy przez słabe odniesienie.
ThreadLocal.get()
jest następnie implementowany w następujący sposób:
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();
}
I ThreadLocal.setInitialValue()
tak:
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;
}
Zasadniczo użyj mapy w tym wątku do przechowywania wszystkich naszych ThreadLocal
obiektów. W ten sposób nigdy nie musimy martwić się o wartości w innych wątkach ( ThreadLocal
dosłownie mamy dostęp tylko do wartości w bieżącym wątku) i dlatego nie mamy problemów ze współbieżnością. Co więcej, po zakończeniu Thread
, jego mapa zostanie automatycznie poddana GC, a wszystkie lokalne obiekty zostaną wyczyszczone. Nawet jeśli Thread
jest trzymany, ThreadLocal
obiekty są trzymane przez słabe odniesienie i można je wyczyścić, gdy tylko ThreadLocal
obiekt znajdzie się poza zasięgiem.
Nie trzeba dodawać, że byłem pod wrażeniem tej implementacji, dość elegancko rozwiązuje wiele problemów związanych ze współbieżnością (wprawdzie dzięki byciu częścią rdzenia Javy, ale jest to wybaczalne, ponieważ jest to tak sprytna klasa) i pozwala na szybkie i bezpieczny wątkowo dostęp do obiektów, do których na raz wystarczy dostęp tylko przez jeden wątek.
ThreadLocal
Implementacja tl; dr jest całkiem fajna i znacznie szybsza / inteligentniejsza niż mogłoby się wydawać na pierwszy rzut oka.
Jeśli spodobała Ci się ta odpowiedź, możesz również docenić moją (mniej szczegółową) dyskusję na tematThreadLocalRandom
.
Thread
/ ThreadLocal
fragmenty kodu pobrane z implementacji Java 8 w Oracle / OpenJDK .
WeakHashMap<String,T>
wprowadza kilka problemów, nie jest wątkowo bezpieczny i „jest przeznaczony głównie do użytku z kluczowymi obiektami, których metody equals testują tożsamość obiektu przy użyciu operatora ==” - więc użycieThread
obiektu jako klucza byłoby lepsze. Sugerowałbym użycie mapy słabe klawisze Guava, którą opisałem powyżej, dla twojego przypadku użycia.Thread.exit()
jest wywoływany i zobaczyszthreadLocals = null;
tam. Komentarz odnosi się do tego błędu, który również może ci się spodobać .Masz na myśli
java.lang.ThreadLocal
. To całkiem proste, naprawdę, to tylko mapa par nazwa-wartość przechowywana w każdymThread
obiekcie (patrzThread.threadLocals
pole). Interfejs API ukrywa ten szczegół implementacji, ale to mniej więcej wszystko.źródło
Zmienne ThreadLocal w Javie działają poprzez dostęp do HashMap przechowywanej przez instancję Thread.currentThread ().
źródło
Załóżmy, że zamierzasz zaimplementować
ThreadLocal
, jak sprawisz, że będzie to specyficzne dla wątku? Oczywiście najprostszą metodą jest utworzenie niestatycznego pola w klasie Thread, nazwijmy tothreadLocals
. Ponieważ każdy wątek jest reprezentowany przez wystąpienie wątku, więcthreadLocals
każdy wątek również byłby inny. I to właśnie robi Java:/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
Co
ThreadLocal.ThreadLocalMap
tu jest ? Ponieważ masz tylkothreadLocals
jeden wątek, więc jeśli po prostu przyjmieszthreadLocals
jako swójThreadLocal
(powiedzmy, zdefiniujesz threadLocals jakoInteger
), będziesz mieć tylko jedenThreadLocal
dla określonego wątku. A co, jeśli chcesz mieć wieleThreadLocal
zmiennych dla wątku? Najprościej jest zrobićthreadLocals
aHashMap
,key
każdy wpis jest nazwąThreadLocal
zmiennej, avalue
każdy wpis jest wartościąThreadLocal
zmiennej. Trochę zagmatwane? Powiedzmy, że mamy dwa wątkit1
it2
. przyjmują to samoRunnable
wystąpienie co parametrThread
konstruktora i obie mają dwieThreadLocal
zmienne o nazwietlA
itlb
. Tak to jest.t1.tlA
+-----+-------+ | Key | Value | +-----+-------+ | tlA | 0 | | tlB | 1 | +-----+-------+
t2.tlB
+-----+-------+ | Key | Value | +-----+-------+ | tlA | 2 | | tlB | 3 | +-----+-------+
Zauważ, że wartości są tworzone przeze mnie.
Teraz wydaje się idealny. Ale co to jest
ThreadLocal.ThreadLocalMap
? Dlaczego po prostu nie użyłHashMap
? Aby rozwiązać problem, zobaczmy, co się stanie, gdy ustawimy wartośćset(T value)
metodąThreadLocal
klasy: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)
po prostu wracat.threadLocals
. Ponieważt.threadLocals
został zainicjowanynull
, więc wchodzimycreateMap(t, value)
pierwszy:void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
Tworzy nową
ThreadLocalMap
instancję, używając bieżącejThreadLocal
instancji i wartości do ustawienia. Zobaczmy,ThreadLocalMap
jak to jest, w rzeczywistości jest częściąThreadLocal
klasystatic 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); } ... }
Rdzeniem tej
ThreadLocalMap
klasy jestEntry class
rozszerzenieWeakReference
. Zapewnia to, że jeśli bieżący wątek zostanie zamknięty, zostanie automatycznie usunięty jako śmieci. Dlatego używaThreadLocalMap
zamiast prostego plikuHashMap
. Przekazuje prądThreadLocal
i jego wartość jako parametrEntry
klasy, więc gdy chcemy pobrać wartość, możemy ją pobrać ztable
, która jest instancjąEntry
klasy: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(); }
Tak wygląda cały obraz:
źródło
Koncepcyjnie możesz myśleć o a
ThreadLocal<T>
jako o trzymaniuMap<Thread,T>
które przechowuje wartości specyficzne dla wątku, chociaż nie jest to sposób, w jaki jest faktycznie implementowany.Wartości specyficzne dla wątku są przechowywane w samym obiekcie Thread; gdy wątek się kończy, wartości specyficzne dla wątku mogą zostać usunięte.
Źródła : JCIP
źródło