¿Cómo se implementa ThreadLocal? ¿Está implementado en Java (usando algún mapa concurrente de ThreadID al objeto), o usa algún gancho JVM para hacerlo de manera más eficiente?
fuente
¿Cómo se implementa ThreadLocal? ¿Está implementado en Java (usando algún mapa concurrente de ThreadID al objeto), o usa algún gancho JVM para hacerlo de manera más eficiente?
Todas las respuestas aquí son correctas, pero un poco decepcionantes, ya que pasan por alto lo inteligente que ThreadLocal
es la implementación. Estaba mirando el código fuenteThreadLocal
y me impresionó gratamente cómo se implementó.
La implementación ingenua
Si te pidiera que implementaras una ThreadLocal<T>
clase dada la API descrita en el javadoc, ¿qué harías? Una implementación inicial probablemente sería un ConcurrentHashMap<Thread,T>
uso Thread.currentThread()
como clave. Esto funcionaría razonablemente bien, pero tiene algunas desventajas.
ConcurrentHashMap
es una clase bastante inteligente, pero en última instancia, todavía tiene que lidiar con la prevención de que varios subprocesos se estropeen de alguna manera, y si diferentes subprocesos lo golpean regularmente, habrá ralentizaciones.La implementación amigable con GC
Ok, inténtalo de nuevo, solucionemos el problema de la recolección de basura utilizando referencias débiles . Tratar con WeakReferences puede ser confuso, pero debería ser suficiente con usar un mapa construido así:
Collections.synchronizedMap(new WeakHashMap<Thread, T>())
O si estamos usando Guava (¡y deberíamos hacerlo!):
new MapMaker().weakKeys().makeMap()
Esto significa que una vez que nadie más se aferra al hilo (lo que implica que está terminado), la clave / valor se puede recolectar como basura, lo cual es una mejora, pero aún no aborda el problema de la contención del hilo, lo que significa que hasta ahora nuestro ThreadLocal
no es todo eso. increíble de una clase. Además, si alguien decidiera aferrarse a los Thread
objetos después de que hayan terminado, nunca recibirían GC y, por lo tanto, tampoco nuestros objetos, a pesar de que ahora son técnicamente inalcanzables.
La implementación inteligente
Hemos estado pensando en ThreadLocal
un mapeo de hilos a valores, pero tal vez esa no sea la forma correcta de pensarlo. En lugar de pensar en ello como un mapeo de Threads a valores en cada objeto ThreadLocal, ¿qué pasaría si lo pensamos como un mapeo de objetos ThreadLocal a valores en cada Thread ? Si cada hilo almacena el mapeo, y ThreadLocal simplemente proporciona una interfaz agradable en ese mapeo, podemos evitar todos los problemas de las implementaciones anteriores.
Una implementación se vería así:
// called for each thread, and updated by the ThreadLocal instance
new WeakHashMap<ThreadLocal,T>()
No hay necesidad de preocuparse por la concurrencia aquí, porque solo un subproceso accederá a este mapa.
Los desarrolladores de Java tienen una gran ventaja sobre nosotros aquí: pueden desarrollar directamente la clase Thread y agregarle campos y operaciones, y eso es exactamente lo que han hecho.
En java.lang.Thread
hay las siguientes líneas:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Lo que, como sugiere el comentario, es de hecho un mapeo privado de paquete de todos los valores que los ThreadLocal
objetos rastrean para esto Thread
. La implementación de ThreadLocalMap
no es un WeakHashMap
, pero sigue el mismo contrato básico, incluida la posesión de sus claves por referencia débil.
ThreadLocal.get()
luego se implementa así:
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();
}
Y ThreadLocal.setInitialValue()
así:
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;
}
Esencialmente, use un mapa en este hilo para contener todos nuestros ThreadLocal
objetos. De esta manera, nunca tenemos que preocuparnos por los valores en otros subprocesos ( ThreadLocal
literalmente, solo podemos acceder a los valores en el subproceso actual) y, por lo tanto, no tenemos problemas de concurrencia. Además, una vez que Thread
haya terminado, su mapa será automáticamente GC'ed y todos los objetos locales se limpiarán. Incluso si Thread
se sujeta, los ThreadLocal
objetos se sujetan con una referencia débil y se pueden limpiar tan pronto como el ThreadLocal
objeto salga de su alcance.
No hace falta decir que me impresionó bastante esta implementación, que soluciona con bastante elegancia muchos problemas de concurrencia (es cierto, aprovechando ser parte del núcleo de Java, pero eso es perdonable, ya que es una clase tan inteligente) y permite una rápida y acceso seguro para subprocesos a objetos a los que solo es necesario acceder un subproceso a la vez.
tl; ThreadLocal
la implementación de dr es bastante buena y mucho más rápida / inteligente de lo que podría pensar a primera vista.
Si le gustó esta respuesta, también puede apreciar mi discusiónThreadLocalRandom
(menos detallada) de .
Thread
/ ThreadLocal
fragmentos de código tomados de la implementación de Oracle / OpenJDK de Java 8 .
WeakHashMap<String,T>
introduce varios problemas, no es seguro para subprocesos y "está diseñado principalmente para usar con objetos clave cuyos métodos iguales prueban la identidad del objeto usando el operador ==", por lo que usar elThread
objeto como clave sería mejor. Sugeriría usar el mapa de teclas débiles de Guava que describo anteriormente para su caso de uso.Thread.exit()
se llama y veráthreadLocals = null;
allí mismo. Un comentario hace referencia a este error que también puede disfrutar leyendo.Te refieres
java.lang.ThreadLocal
. Es bastante simple, en realidad, es solo un mapa de pares de nombre-valor almacenados dentro de cadaThread
objeto (ver elThread.threadLocals
campo). La API oculta ese detalle de implementación, pero eso es más o menos todo lo que hay que hacer.fuente
Las variables ThreadLocal en Java funcionan accediendo a un HashMap en poder de la instancia Thread.currentThread ().
fuente
Suponga que va a implementar
ThreadLocal
, ¿cómo lo hace específico para un hilo? Por supuesto, el método más simple es crear un campo no estático en la clase Thread, llamémoslothreadLocals
. Debido a que cada hilo está representado por una instancia de hilo,threadLocals
en cada hilo también sería diferente. Y esto también es lo que hace Java:/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
¿Qué hay
ThreadLocal.ThreadLocalMap
aquí? Debido a que solo tiene unthreadLocals
para un hilo, si simplemente tomathreadLocals
como suThreadLocal
(digamos, defina threadLocals comoInteger
), solo tendrá unoThreadLocal
para un hilo específico. ¿Qué pasa si quieres múltiplesThreadLocal
variables para un hilo? La forma más sencilla es hacerthreadLocals
unHashMap
, elkey
de cada entrada es el nombre de laThreadLocal
variable y elvalue
de cada entrada es el valor de laThreadLocal
variable. ¿Un poco confuso? Digamos que tenemos dos hilost1
yt2
. toman la mismaRunnable
instancia que el parámetro deThread
constructor, y ambos tienen dosThreadLocal
variables llamadastlA
ytlb
. Así es como es.t1.tlA
+-----+-------+ | Key | Value | +-----+-------+ | tlA | 0 | | tlB | 1 | +-----+-------+
t2.tlB
+-----+-------+ | Key | Value | +-----+-------+ | tlA | 2 | | tlB | 3 | +-----+-------+
Tenga en cuenta que los valores los invento yo.
Ahora parece perfecto. Pero lo que es
ThreadLocal.ThreadLocalMap
? ¿Por qué no lo usóHashMap
? Para resolver el problema, veamos qué sucede cuando establecemos un valor mediante elset(T value)
método de laThreadLocal
clase: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)
simplemente regresat.threadLocals
. Porquet.threadLocals
fue iniciado anull
, entonces ingresamoscreateMap(t, value)
primero:void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
Crea una nueva
ThreadLocalMap
instancia usando laThreadLocal
instancia actual y el valor que se va a establecer. Veamos cómoThreadLocalMap
es, de hecho es parte de laThreadLocal
clase.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); } ... }
La parte central de la
ThreadLocalMap
clase esEntry class
, que se extiendeWeakReference
. Asegura que si el hilo actual sale, se recolectará la basura automáticamente. Es por eso que usa enThreadLocalMap
lugar de un simpleHashMap
. Pasa la corrienteThreadLocal
y su valor como parámetro de laEntry
clase, por lo que cuando queremos obtener el valor, podemos obtenerlo detable
, que es una instancia de laEntry
clase: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(); }
Así es como se ve en la imagen completa:
fuente
Conceptualmente, puede pensar
ThreadLocal<T>
que a contiene unMap<Thread,T>
que almacena los valores específicos del hilo, aunque no es así como se implementa realmente.Los valores específicos del hilo se almacenan en el propio objeto Thread; cuando el subproceso termina, los valores específicos del subproceso se pueden recolectar como basura.
Referencia: JCIP
fuente