¿Por qué las variables Java ThreadLocal deberían ser estáticas?

101

Estaba leyendo JavaDoc para Threadlocal aquí

https://docs.oracle.com/javase/1.5.0/docs/api/java/lang/ThreadLocal.html

y dice "Las instancias ThreadLocal son típicamente campos estáticos privados en clases que desean asociar el estado con un hilo (por ejemplo, un ID de usuario o un ID de transacción)".

Pero mi pregunta es ¿por qué eligieron hacerlo estático (normalmente) - hace que las cosas sean un poco confusas tener un estado "por hilo" pero los campos son estáticos?

Kellyfj
fuente

Respuestas:

131

Porque si fuera un campo de nivel de instancia, en realidad sería "Por subproceso - Por instancia", no solo un "Por subproceso" garantizado. Normalmente, esa no es la semántica que estás buscando.

Por lo general, contiene algo como objetos que tienen como alcance una conversación de usuario, una solicitud web, etc. No desea que también tengan un alcance secundario a la instancia de la clase.
Una solicitud web => una sesión de persistencia.
Ni una solicitud web => una sesión de persistencia por objeto.

Affe
fuente
2
Me gusta esta explicación porque muestra cómo se debe usar
ThreadLocal
4
Por subproceso por instancia puede ser una semántica útil, pero la mayoría de los usos para ese patrón involucrarían tantos objetos que sería mejor usar a ThreadLocalpara mantener una referencia a un conjunto de hash que mapea objetos a instancias por subproceso.
supercat
@optional Simplemente significa que cada instancia de lo no estático ThreadLocalcontendría sus propios datos locales de subproceso incluso si esas ThreadLocalinstancias existen en el mismo subproceso. No es necesariamente incorrecto hacer eso, supongo que podría ser el patrón menos popular de los dos
geg
17

Hágalo estático o si está tratando de evitar cualquier campo estático en su clase, haga que la clase en sí sea un singleton y luego puede usar de manera segura el ThreadLocal de nivel de instancia siempre que tenga ese singleton disponible globalmente.

Adnan Memon
fuente
12

No tiene por qué serlo. Lo importante es que debe ser singleton.

irrefutable
fuente
3

La razón es que se accede a las variables a través de un puntero asociado con el hilo. Actúan como variables globales con alcance de hilo, por lo tanto, estático es el ajuste más cercano. Esta es la forma en que obtiene el estado local del hilo en cosas como pthreads, por lo que esto podría ser un accidente del historial y la implementación.

Ukko
fuente
1

Un uso de un threadlocal en una instancia por subproceso es si desea que algo sea visible en todos los métodos de un objeto y que sea seguro para subprocesos sin sincronizar el acceso a él como lo haría para un campo ordinario.

Chris Mawata
fuente
1

Refiérase a esto , esto le dará una mejor comprensión.

En resumen, el ThreadLocalobjeto funciona como un mapa clave-valor. Cuando el ThreadLocal get/setmétodo de invocación del hilo , recuperará / almacenará el objeto del hilo en la clave del mapa y el valor en el valor del mapa. Es por eso que diferentes subprocesos tienen diferentes copias de valor (que desea almacenar localmente), porque residen en diferentes entradas de mapas.

Es por eso que solo necesita un mapa para mantener todos los valores. Aunque no es necesario, puede tener múltiples mapas (sin declarar estático) para mantener cada objeto de hilo también, lo cual es totalmente redundante, por eso se prefiere la variable estática.

GMsoF
fuente
-1

static final ThreadLocal las variables son seguras para subprocesos.

statichace que la variable ThreadLocal esté disponible en varias clases solo para el hilo respectivo. es una especie de decantación de variable global de las respectivas variables locales de subproceso en múltiples clases.

Podemos comprobar la seguridad de este subproceso con el siguiente ejemplo de código.

  • CurrentUser - almacena la identificación del usuario actual en ThreadLocal
  • TestService- Servicio simple con método: getUser()para recuperar el usuario actual de CurrentUser.
  • TestThread - esta clase se usa para crear múltiples subprocesos y establecer ID de usuario al mismo tiempo

.

public class CurrentUser

public class CurrentUser {
private static final ThreadLocal<String> CURRENT = new ThreadLocal<String>();

public static ThreadLocal<String> getCurrent() {
    return CURRENT;
}

public static void setCurrent(String user) {
    CURRENT.set(user);
}

}

public class TestService {

public String getUser() {
    return CurrentUser.getCurrent().get();
}

}

.

import java.util.ArrayList;
import java.util.List;

public class TestThread {

public static void main(String[] args) {

  List<Integer> integerList = new ArrayList<>();

  //creates a List of 100 integers
  for (int i = 0; i < 100; i++) {

    integerList.add(i);
  }

  //parallel stream to test concurrent thread execution
  integerList.parallelStream().forEach(intValue -> {

    //All concurrent thread will set the user as "intValue"
    CurrentUser.setCurrent("" + intValue);
    //Thread creates a sample instance for TestService class
    TestService testService = new TestService();
    //Print the respective thread name along with "intValue" value and current user. 
    System.out.println("Start-"+Thread.currentThread().getName()+"->"+intValue + "->" + testService.getUser());

    try {
      //all concurrent thread will wait for 3 seconds
      Thread.sleep(3000l);
    } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }

    //Print the respective thread name along with "intValue" value and current user.
    System.out.println("End-"+Thread.currentThread().getName()+"->"+intValue + "->" + testService.getUser());
  });

}

}

.

Ejecute la clase principal TestThread. Salida -

Start-main->62->62
Start-ForkJoinPool.commonPool-worker-2->31->31
Start-ForkJoinPool.commonPool-worker-3->81->81
Start-ForkJoinPool.commonPool-worker-1->87->87
End-main->62->62
End-ForkJoinPool.commonPool-worker-1->87->87
End-ForkJoinPool.commonPool-worker-2->31->31
End-ForkJoinPool.commonPool-worker-3->81->81
Start-ForkJoinPool.commonPool-worker-2->32->32
Start-ForkJoinPool.commonPool-worker-3->82->82
Start-ForkJoinPool.commonPool-worker-1->88->88
Start-main->63->63
End-ForkJoinPool.commonPool-worker-1->88->88
End-main->63->63
...

Resumen de análisis

  1. El subproceso "principal" se inicia y configura el usuario actual como "62", paralelamente se inicia el subproceso "ForkJoinPool.commonPool-worker-2" y configura el usuario actual como "31", paralelamente se inicia el subproceso "ForkJoinPool.commonPool-worker-3" y configura el actual usuario como "81", paralelamente se inicia el hilo "ForkJoinPool.commonPool-worker-1" y establece el usuario actual como "87" Start-main-> 62-> 62 Start-ForkJoinPool.commonPool-worker-2-> 31-> 31 Start-ForkJoinPool.commonPool-worker-3-> 81-> 81 Start-ForkJoinPool.commonPool-worker-1-> 87-> 87
  2. Todos estos hilos anteriores dormirán durante 3 segundos
  3. mainla ejecución finaliza e imprime el usuario actual como "62", la ForkJoinPool.commonPool-worker-1ejecución paralela finaliza e imprime el usuario actual como "87", la ForkJoinPool.commonPool-worker-2ejecución paralela termina e imprime el usuario actual como "31", la ForkJoinPool.commonPool-worker-3ejecución paralela termina e imprime el usuario actual como "81"

Inferencia

Los subprocesos concurrentes pueden recuperar los ID de usuario correctos incluso si se han declarado como "ThreadLocal final estático"

shijin raj
fuente