¿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?
¿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?
Respuestas:
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 el Thread
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 cada Thread
objeto (ver el Thread.threadLocals
campo). La API oculta ese detalle de implementación, pero eso es más o menos todo lo que hay que hacer.
Las variables ThreadLocal en Java funcionan accediendo a un HashMap en poder de la instancia Thread.currentThread ().
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émoslo threadLocals
. 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 un threadLocals
para un hilo, si simplemente toma threadLocals
como su ThreadLocal
(digamos, defina threadLocals como Integer
), solo tendrá uno ThreadLocal
para un hilo específico. ¿Qué pasa si quieres múltiples ThreadLocal
variables para un hilo? La forma más sencilla es hacer threadLocals
un HashMap
, el key
de cada entrada es el nombre de la ThreadLocal
variable y el value
de cada entrada es el valor de la ThreadLocal
variable. ¿Un poco confuso? Digamos que tenemos dos hilos t1
y t2
. toman la misma Runnable
instancia que el parámetro de Thread
constructor, y ambos tienen dos ThreadLocal
variables llamadas tlA
y tlb
. 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 el set(T value)
método de la ThreadLocal
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 regresa t.threadLocals
. Porque t.threadLocals
fue iniciado a null
, entonces ingresamos createMap(t, value)
primero:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Crea una nueva ThreadLocalMap
instancia usando la ThreadLocal
instancia actual y el valor que se va a establecer. Veamos cómo ThreadLocalMap
es, de hecho es parte de la ThreadLocal
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 es Entry class
, que se extiende WeakReference
. Asegura que si el hilo actual sale, se recolectará la basura automáticamente. Es por eso que usa en ThreadLocalMap
lugar de un simple HashMap
. Pasa la corriente ThreadLocal
y su valor como parámetro de la Entry
clase, por lo que cuando queremos obtener el valor, podemos obtenerlo de table
, que es una instancia de la Entry
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:
Conceptualmente, puede pensar ThreadLocal<T>
que a contiene un Map<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