2009-07-29 13 views

Respuesta

84

Todas las respuestas aquí son correctas, pero un poco decepcionantes ya que de alguna manera no se sabe cuán inteligente es la implementación de ThreadLocal. Solo estaba mirando el source code for ThreadLocal y quedé gratamente impresionado con la forma en que se implementó.

la implementación Naive

Si te pidiera que implementar una clase ThreadLocal<T> dada la API se describe en el Javadoc, ¿qué harías? Una implementación inicial probablemente sería ConcurrentHashMap<Thread,T> usando Thread.currentThread() como su clave. Esto funcionaría razonablemente bien, pero tiene algunas desventajas.

  • hilo de contención - ConcurrentHashMap es una clase muy inteligente, pero en última instancia, todavía tiene que lidiar con la prevención de múltiples hilos de ensuciar con él de ninguna manera, y si diferentes hilos golpean regularmente, habrá retrasos.
  • Permanentemente mantiene un puntero tanto al hilo como al objeto, incluso después de que el hilo haya terminado y pueda ser sometido a un GC.

La GC-amigable Implementación

intentarlo de nuevo Ok, vamos a tratar el tema de recolección de basura mediante el uso de weak references. Tratar con WeakReferences puede ser confuso, pero debería ser suficiente para utilizar un mapa construido de esta manera: (! Y que debe ser)

Collections.synchronizedMap(new WeakHashMap<Thread, T>()) 

O si estamos usando Guava:

new MapMaker().weakKeys().makeMap() 

Este significa que una vez que nadie más se aferra al hilo (lo que implica que ha terminado) la clave/valor puede ser basura, lo cual es una mejora, pero aún no soluciona el problema de contención de hilos, lo que significa que hasta ahora nuestro ThreadLocal no es todo increíble de una clase. Además, si alguien decidiera retener los objetos Thread después de que hubieran terminado, nunca recibirían GC, y por lo tanto tampoco nuestros objetos, aunque ahora son técnicamente inalcanzables.

la implementación inteligente

Hemos estado pensando acerca ThreadLocal como una asignación de temas a los valores, pero tal vez eso no es realmente la manera correcta de pensar en ello. En lugar de pensar en ello como un mapeo de Threads a valores en cada objeto ThreadLocal, ¿qué pasaría si lo consideráramos una asignación de objetos ThreadLocal a los valores en cada Thread?Si cada hilo almacena la asignación, y ThreadLocal simplemente proporciona una interfaz agradable en esa asignación, podemos evitar todos los problemas de las implementaciones anteriores.

Una implementación sería algo como esto:

// called for each thread, and updated by the ThreadLocal instance 
new WeakHashMap<ThreadLocal,T>() 

No hay necesidad de preocuparse por la concurrencia aquí, porque sólo un hilo siempre tendrá acceso a este mapa.

Los desarrolladores 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; 

que como su comentario sugiere es de hecho un mapeo paquete-privada de todos los valores de ser rastreado por ThreadLocal objetos para este Thread. La implementación de ThreadLocalMap no es WeakHashMap, pero sigue el mismo contrato básico, que incluye mantener sus claves por referencia débil.

ThreadLocal.get() se implementa a continuación de este modo:

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, utilice un mapa en este Tema para mantener todas nuestros objetos ThreadLocal. De esta forma, nunca tenemos que preocuparnos por los valores en otros subprocesos (ThreadLocal, literalmente, solo pueden acceder a los valores en el subproceso actual) y, por lo tanto, no tienen problemas de concurrencia. Además, una vez que se completa el Thread, su mapa se someterá automáticamente a GC y se limpiarán todos los objetos locales. Incluso si el Thread se mantiene, los objetos ThreadLocal se mantienen por referencia débil, y se pueden limpiar tan pronto como el objeto ThreadLocal quede fuera del alcance.


Ni que decir tiene, que estaba bastante impresionado por esta aplicación, es bastante elegante recibe alrededor de un montón de problemas de concurrencia (la verdad, tomando ventaja de ser parte del núcleo de Java, pero eso es perdonable ellos ya que es una ingeniosa tales clase) y permite un acceso rápido y seguro a subprocesos a objetos a los que solo se debe acceder mediante un subproceso a la vez.

tl; drThreadLocal la implementación es bastante buena, y mucho más rápida/más inteligente de lo que piensas a primera vista.

Si te gustó esta respuesta, puedes ver que me gusta (menos información) discussion of ThreadLocalRandom. fragmentos

Thread/ThreadLocal código tomado de Oracle/OpenJDK's implementation of Java 8.

+1

Tus respuestas se ven increíbles, pero ahora es demasiado tiempo para mí. +1 y aceptado, y lo estoy agregando a mi cuenta de getpocket.com para leerlo más tarde. ¡Gracias! – ripper234

+0

Necesito una cosa similar a ThreadLocal que también me permita acceder a la lista completa de valores, al igual que map.values ​​(). Entonces, mi implementación ingenua es WeakHashMap donde la clave es Thread.currentThread(). GetName(). Esto evita la referencia al hilo en sí. Si el hilo se va, entonces nada retiene el nombre del hilo (una suposición, lo admito) y mi valor desaparecerá. – bmauter

+0

En realidad, [respondí una pregunta a tal efecto recientemente] (http://stackoverflow.com/a/15654081/113632). Un 'WeakHashMap ' presenta varios problemas, no es seguro para el hilo, y "está destinado principalmente para usar objetos claves cuyos métodos iguales prueban la identidad del objeto usando el operador ==", así que usar el objeto 'Thread' como una clave hacerlo mejor. Sugiero usar el mapa de Keys débiles de Guava que describí arriba para su caso de uso. – dimo414

7

Las variables de ThreadLocal en Java funcionan accediendo a un HashMap mantenido por la instancia Thread.currentThread().

+0

Eso no es correcto (o al menos no lo es ya). Thread.currentThread() es una llamada nativa en Thread.class. También Thread tiene un "ThreadLocalMap" que es un solo grupo (matriz) de códigos hash. Este objeto no es compatible con la interfaz de Mapa. – user924272

+1

Eso es básicamente lo que dije. currentThread() devuelve una instancia Thread, que contiene un mapa de ThreadLocals a valores. –

+0

¡No es un mapa! – user924272

29

Quiere significar java.lang.ThreadLocal. Es bastante simple, realmente, es solo un Mapa de pares de nombre-valor almacenados dentro de cada objeto Thread (vea el campo Thread.threadLocals). La API oculta ese detalle de implementación, pero eso es más o menos todo lo que hay que hacer.

+0

Entonces, no incurre en bloqueos o contención, ¿verdad? – ripper234

+0

No puedo ver por qué debería ser necesario, dado que, por definición, los datos solo son visibles para un solo hilo. – skaffman

+7

Correcto, no hay sincronización ni bloqueo alrededor o dentro de ThreadLocalMap, ya que solo se accede in-thread. – Cowan

1

Supongamos que va a implementar ThreadLocal, ¿cómo lo hace específico de subprocesos? 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, por lo que threadLocals en cada hilo sería diferente, también. Y esto es también lo que hace de Java:

/* ThreadLocal values pertaining to this thread. This map is maintained 
* by the ThreadLocal class. */ 
ThreadLocal.ThreadLocalMap threadLocals = null; 

Lo que es ThreadLocal.ThreadLocalMap aquí? Como solo tiene un threadLocals para un hilo, entonces si simplemente toma threadLocals como su ThreadLocal (digamos, define threadLocals como Integer), solo tendrá un ThreadLocal para un hilo específico. ¿Qué sucede si quiere múltiples variables ThreadLocal para un hilo? La forma más simple es hacer threadLocals un HashMap, el key de cada entrada es el nombre de la variable ThreadLocal, y el value de cada entrada es el valor de la variable ThreadLocal. Un poco confuso? Digamos que tenemos dos hilos, t1 y t2. toman la misma instancia Runnable como el parámetro del constructor Thread, y ambos tienen dos variables ThreadLocal llamadas tlA y tlb. Esto es lo que es.

t1.tlA

+-----+-------+ 
| Key | Value | 
+-----+-------+ 
| tlA |  0 | 
| tlB |  1 | 
+-----+-------+ 

t2.tlB

+-----+-------+ 
| Key | Value | 
+-----+-------+ 
| tlA |  2 | 
| tlB |  3 | 
+-----+-------+ 

en cuenta que los valores están compuestos por mí.

Ahora parece perfecto. ¿Pero qué es ThreadLocal.ThreadLocalMap? ¿Por qué no solo usó HashMap? Para resolver el problema, vamos a ver lo que sucede cuando fijamos un valor a través del método de la clase ThreadLocalset(T value):

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 devuelve t.threadLocals. Debido a t.threadLocals se initilized null, así que entramos createMap(t, value) primera:

void createMap(Thread t, T firstValue) { 
    t.threadLocals = new ThreadLocalMap(this, firstValue); 
} 

Se crea una nueva instancia utilizando ThreadLocalMap la corriente ThreadLocal instancia y el valor que se creará. Vamos a ver lo que es como ThreadLocalMap, es de hecho parte de la clase ThreadLocal

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 clase ThreadLocalMap es la Entry class, que se extiende WeakReference. Asegura que si sale el hilo actual, se recolectará basura automáticamente. Es por eso que usa ThreadLocalMap en lugar de un simple HashMap.Se pasa la corriente ThreadLocal y su valor como parámetro de la clase Entry, por lo que cuando queremos obtener el valor, que podría obtener de table, que es una instancia de la clase Entry:

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(); 
} 

Este es lo que es como en todo el cuadro:

The Whole Picture

0

conceptualmente, se puede pensar en un ThreadLocal<T> como la celebración de un Map<Thread,T> que almacena los valores de rosca específico, aunque esto no es lo que es en realidad implementado.

Los valores específicos de hilo se almacenan en el objeto Thread mismo; cuando el hilo termina, los valores específicos del hilo pueden ser recogidos.

Referencia: JCIP

+0

Conceptalmente, sí. Pero como ve en otras respuestas anteriores, la implementación es completamente diferente. – Archit

Cuestiones relacionadas