¿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 ThreadLocales 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.
ConcurrentHashMapes 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 ThreadLocalno es todo eso. increíble de una clase. Además, si alguien decidiera aferrarse a los Threadobjetos 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 ThreadLocalun 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.Threadhay 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 ThreadLocalobjetos rastrean para esto Thread. La implementación de ThreadLocalMapno 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 ThreadLocalobjetos. De esta manera, nunca tenemos que preocuparnos por los valores en otros subprocesos ( ThreadLocalliteralmente, solo podemos acceder a los valores en el subproceso actual) y, por lo tanto, no tenemos problemas de concurrencia. Además, una vez que Threadhaya terminado, su mapa será automáticamente GC'ed y todos los objetos locales se limpiarán. Incluso si Threadse sujeta, los ThreadLocalobjetos se sujetan con una referencia débil y se pueden limpiar tan pronto como el ThreadLocalobjeto 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/ ThreadLocalfragmentos 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 elThreadobjeto 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 cadaThreadobjeto (ver elThread.threadLocalscampo). 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,threadLocalsen 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.ThreadLocalMapaquí? Debido a que solo tiene unthreadLocalspara un hilo, si simplemente tomathreadLocalscomo suThreadLocal(digamos, defina threadLocals comoInteger), solo tendrá unoThreadLocalpara un hilo específico. ¿Qué pasa si quieres múltiplesThreadLocalvariables para un hilo? La forma más sencilla es hacerthreadLocalsunHashMap, elkeyde cada entrada es el nombre de laThreadLocalvariable y elvaluede cada entrada es el valor de laThreadLocalvariable. ¿Un poco confuso? Digamos que tenemos dos hilost1yt2. toman la mismaRunnableinstancia que el parámetro deThreadconstructor, y ambos tienen dosThreadLocalvariables llamadastlAytlb. 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 laThreadLocalclase: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.threadLocalsfue iniciado anull, entonces ingresamoscreateMap(t, value)primero:void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }Crea una nueva
ThreadLocalMapinstancia usando laThreadLocalinstancia actual y el valor que se va a establecer. Veamos cómoThreadLocalMapes, de hecho es parte de laThreadLocalclase.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
ThreadLocalMapclase esEntry class, que se extiendeWeakReference. Asegura que si el hilo actual sale, se recolectará la basura automáticamente. Es por eso que usa enThreadLocalMaplugar de un simpleHashMap. Pasa la corrienteThreadLocaly su valor como parámetro de laEntryclase, por lo que cuando queremos obtener el valor, podemos obtenerlo detable, que es una instancia de laEntryclase: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