java multithreading concurrency thread-local thread-confinement

java - ¿Cuándo y cómo debo usar una variable ThreadLocal?



multithreading concurrency (24)

ThreadLocal asegurará que el acceso al objeto mutable por los múltiples hilos en el método no sincronizado esté sincronizado, lo que hace que el objeto mutable sea inmutable dentro del método.

Esto se logra dando una nueva instancia de objeto mutable para cada subproceso que intenta acceder a él. Así que es copia local a cada hilo. Este es un truco para hacer que la variable de instancia en un método sea accesible como una variable local. Como sabe, la variable local del método solo está disponible para el subproceso, una diferencia es; las variables locales del método no estarán disponibles para el hilo una vez que la ejecución del método haya terminado, ya que un objeto mutable compartido con threadlocal estará disponible a través de múltiples métodos hasta que lo limpiemos.

Por definición:

La clase ThreadLocal en Java le permite crear variables que solo pueden leerse y escribirse en el mismo hilo. Por lo tanto, incluso si dos subprocesos están ejecutando el mismo código, y el código tiene una referencia a una variable ThreadLocal, entonces los dos subprocesos no pueden ver las variables ThreadLocal de cada uno.

Cada Thread en java contiene ThreadLocalMap en él.
Dónde

Key = One ThreadLocal object shared across threads. value = Mutable object which has to be used synchronously, this will be instantiated for each thread.

Logrando el ThreadLocal:

Ahora cree una clase envoltura para ThreadLocal que mantendrá el objeto mutable como abajo (con o sin initialValue() ).
Ahora getter y setter de esta envoltura trabajarán en una instancia de threadlocal en lugar de un objeto mutable.

Si getter () de threadlocal no encontró ningún valor en el threadlocalmap del Thread ; entonces invocará el valor inicial () para obtener su copia privada con respecto al hilo.

class SimpleDateFormatInstancePerThread { private static final ThreadLocal<SimpleDateFormat> dateFormatHolder = new ThreadLocal<SimpleDateFormat>() { @Override protected SimpleDateFormat initialValue() { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd") { UUID id = UUID.randomUUID(); @Override public String toString() { return id.toString(); }; }; System.out.println("Creating SimpleDateFormat instance " + dateFormat +" for Thread : " + Thread.currentThread().getName()); return dateFormat; } }; /* * Every time there is a call for DateFormat, ThreadLocal will return calling * Thread''s copy of SimpleDateFormat */ public static DateFormat getDateFormatter() { return dateFormatHolder.get(); } public static void cleanup() { dateFormatHolder.remove(); } }

Ahora wrapper.getDateFormatter() llamará a threadlocal.get() y eso comprobará que currentThread.threadLocalMap contiene esta instancia (threadlocal).
Si es así, devuelva el valor (SimpleDateFormat) para la instancia de threadlocal correspondiente
de lo contrario, agregue el mapa con esta instancia de threadlocal, initialValue ().

Con esto, la seguridad del hilo lograda en esta clase mutable; Cada subproceso está trabajando con su propia instancia mutable pero con la misma instancia de ThreadLocal. Significa que todo el hilo compartirá la misma instancia de ThreadLocal como clave, pero diferente instancia de SimpleDateFormat como valor.

https://github.com/skanagavelu/yt.tech/blob/master/src/ThreadLocalTest.java

¿Cuándo debo usar una variable ThreadLocal ?

¿Cómo se usa?


  1. ThreadLocal en Java se introdujo en JDK 1.2, pero luego se generó en JDK 1.5 para introducir la seguridad de tipos en la variable ThreadLocal.

  2. ThreadLocal puede asociarse con Thread scope, todo el código que ejecuta Thread tiene acceso a las variables ThreadLocal, pero dos subprocesos no pueden ver la variable ThreadLocal.

  3. Cada subproceso contiene una copia exclusiva de la variable ThreadLocal que se vuelve elegible para la recolección de basura después de que el subproceso haya finalizado o haya muerto, normalmente o debido a alguna Excepción. Dada la variable ThreadLocal no tiene otras referencias en vivo.

  4. Las variables ThreadLocal en Java son generalmente campos estáticos privados en Clases y mantienen su estado dentro de Thread.

Lea más: http://javarevisited.blogspot.com/2012/05/how-to-use-threadlocal-in-java-benefits.html#ixzz2XltgbHTK


Almacenamiento en caché: en algún momento debe calcular el mismo valor durante mucho tiempo, así que al almacenar el último conjunto de entradas en un método y el resultado, puede acelerar el código. Al utilizar Thread Local Storage, evita tener que pensar en bloquear.


Como mencionó @unknown (google), su uso es definir una variable global en la que el valor al que se hace referencia puede ser único en cada subproceso. Sus usos generalmente implican el almacenamiento de algún tipo de información contextual que está vinculada al hilo de ejecución actual.

Lo usamos en un entorno Java EE para pasar la identidad del usuario a clases que no son compatibles con Java EE (no tienen acceso a HttpSession o EJB SessionContext). De esta manera, el código, que hace uso de la identidad para las operaciones basadas en la seguridad, puede acceder a la identidad desde cualquier lugar, sin tener que pasarla explícitamente en cada llamada de método.

El ciclo de operaciones de solicitud / respuesta en la mayoría de las llamadas de Java EE facilita este tipo de uso, ya que proporciona puntos de entrada y salida bien definidos para configurar y anular ThreadLocal.


Dado que un ThreadLocal es una referencia a los datos dentro de un Thread dado, puede terminar con pérdidas de ThreadLocal clases al usar ThreadLocal en servidores de aplicaciones que usan grupos de subprocesos. ThreadLocal tener mucho cuidado al limpiar cualquier ThreadLocal s que get() o set() usando el ThreadLocal remove() ThreadLocal .

Si no realiza la limpieza cuando termina, cualquier referencia que contenga a las clases cargadas como parte de una aplicación web implementada permanecerá en el montón permanente y nunca se recogerá la basura. La implementación / desinstalación de la aplicación web no limpiará la referencia de cada Thread a la (s) clase (s) de su aplicación web, ya que el Thread no es algo que pertenezca a su aplicación web. Cada implementación sucesiva creará una nueva instancia de la clase que nunca se recolectará como basura.

Terminará con las excepciones de falta de memoria debido a java.lang.OutOfMemoryError: PermGen space y después de algunas -XX:MaxPermSize en Google probablemente aumentará -XX:MaxPermSize lugar de corregir el error.

Si terminas experimentando estos problemas, puedes determinar qué hilo y clase está reteniendo estas referencias usando el Analizador de memoria de Eclipse y / o siguiendo la guía y el followup Frank Kieviet .

Actualización: Re-descubrí la entrada del blog de Alex Vasseur que me ayudó a localizar algunos problemas de ThreadLocal que estaba teniendo.


Desde el lanzamiento de Java 8, hay una manera más declarativa de inicializar ThreadLocal :

ThreadLocal<Cipher> local = ThreadLocal.withInitial(() -> "init value");

Hasta el lanzamiento de Java 8 tenías que hacer lo siguiente:

ThreadLocal<String> local = new ThreadLocal<String>(){ @Override protected String initialValue() { return "init value"; } };

Además, si el método de creación de instancias (constructor, método de fábrica) de la clase que se usa para ThreadLocal no toma ningún parámetro, simplemente puede usar referencias de métodos (introducidas en Java 8):

class NotThreadSafe { // no parameters public NotThreadSafe(){} } ThreadLocal<NotThreadSafe> container = ThreadLocal.withInitial(NotThreadSafe::new);

Nota: la evaluación es perezosa ya que está pasando java.util.function.Supplier lambda que se evalúa solo cuando se llama a ThreadLocal#get pero el valor no se evaluó previamente.


Dos casos de uso donde se puede usar la variable threadlocal -
1- Cuando tenemos el requisito de asociar el estado con un subproceso (por ejemplo, un ID de usuario o un ID de transacción). Por lo general, esto sucede con una aplicación web en la que cada solicitud dirigida a un servlet tiene un ID de transacción único asociado.

// This class will provide a thread local variable which // will provide a unique ID for each thread class ThreadId { // Atomic integer containing the next thread ID to be assigned private static final AtomicInteger nextId = new AtomicInteger(0); // Thread local variable containing each thread''s ID private static final ThreadLocal<Integer> threadId = ThreadLocal.<Integer>withInitial(()-> {return nextId.getAndIncrement();}); // Returns the current thread''s unique ID, assigning it if necessary public static int get() { return threadId.get(); } }

Tenga en cuenta que aquí el método withInitial se implementa utilizando la expresión lambda.
2- Otro caso de uso es cuando queremos tener una instancia segura para subprocesos y no queremos usar la sincronización, ya que el costo de rendimiento con la sincronización es mayor. Uno de estos casos es cuando se usa SimpleDateFormat. Dado que SimpleDateFormat no es seguro para subprocesos, tenemos que proporcionar un mecanismo para hacerlo seguro.

public class ThreadLocalDemo1 implements Runnable { // threadlocal variable is created private static final ThreadLocal<SimpleDateFormat> dateFormat = new ThreadLocal<SimpleDateFormat>(){ @Override protected SimpleDateFormat initialValue(){ System.out.println("Initializing SimpleDateFormat for - " + Thread.currentThread().getName() ); return new SimpleDateFormat("dd/MM/yyyy"); } }; public static void main(String[] args) { ThreadLocalDemo1 td = new ThreadLocalDemo1(); // Two threads are created Thread t1 = new Thread(td, "Thread-1"); Thread t2 = new Thread(td, "Thread-2"); t1.start(); t2.start(); } @Override public void run() { System.out.println("Thread run execution started for " + Thread.currentThread().getName()); System.out.println("Date formatter pattern is " + dateFormat.get().toPattern()); System.out.println("Formatted date is " + dateFormat.get().format(new Date())); } }


El servidor de la aplicación web puede mantener un grupo de subprocesos, y una var ThreadLocal debe eliminarse antes de responder al cliente, por lo que el subproceso actual puede reutilizarse en la próxima solicitud.


En Java, si tiene un dato que puede variar por subproceso, sus opciones son pasar ese dato a todos los métodos que lo necesiten (o puedan necesitar), o asociar el dato con el subproceso. Pasar el dato a todas partes puede ser viable si todos sus métodos ya necesitan pasar una variable de "contexto" común.

Si ese no es el caso, es posible que no desee saturar las firmas de su método con un parámetro adicional. En un mundo sin hilos, podría resolver el problema con el equivalente de Java de una variable global. En una palabra con hilos, el equivalente de una variable global es una variable de hilo local.


Esencialmente, cuando necesita el valor de una variable para depender del subproceso actual y no es conveniente adjuntar el valor al subproceso de alguna otra manera (por ejemplo, subproceso de subclase).

Un caso típico es cuando algún otro marco ha creado el hilo en el que se está ejecutando su código, por ejemplo, un contenedor de servlets, o donde tiene más sentido usar ThreadLocal porque su variable está "en su lugar lógico" (en lugar de una variable). colgando de una subclase Thread o en algún otro mapa hash).

En mi sitio web, tengo una discusión adicional y ejemplos de cuándo usar ThreadLocal que también pueden ser de interés.

Algunas personas abogan por usar ThreadLocal como una forma de adjuntar un "ID de hilo" a cada hilo en ciertos algoritmos concurrentes donde necesita un número de hilo (consulte, por ejemplo, Herlihy y Shavit). En tales casos, ¡compruebe que realmente está obteniendo un beneficio!


Hay muy buen ejemplo en libro Java Concurrencia en la práctica . Donde el autor ( Joshua Bloch ) explica cómo el confinamiento de subprocesos es una de las formas más sencillas de lograr la seguridad de subprocesos y ThreadLocal es un medio más formal de mantener el confinamiento de subprocesos. Al final, también explica cómo las personas pueden abusar de él usándolo como variables globales.

He copiado el texto del libro mencionado pero falta el código 3.10 ya que no es muy importante entender dónde se debe usar ThreadLocal.

Las variables de subprocesos locales se utilizan a menudo para evitar compartir en diseños basados ​​en Singletons o variables globales mutables. Por ejemplo, una aplicación de un solo subproceso puede mantener una conexión de base de datos global que se inicializa en el inicio para evitar tener que pasar una conexión a cada método. Como las conexiones JDBC pueden no ser seguras para subprocesos, una aplicación multihilo que usa una conexión global sin coordinación adicional tampoco es segura para subprocesos. Al usar un ThreadLocal para almacenar la conexión JDBC, como en ConnectionHolder en el Listado 3.10, cada hilo tendrá su propia conexión.

ThreadLocal se usa ampliamente en la implementación de marcos de aplicaciones. Por ejemplo, los contenedores J2EE asocian un contexto de transacción con un subproceso en ejecución durante la duración de una llamada EJB. Esto se implementa fácilmente utilizando un Thread-Local estático que contiene el contexto de la transacción: cuando el código del marco necesita determinar qué transacción se está ejecutando actualmente, obtiene el contexto de la transacción de este ThreadLocal. Esto es conveniente porque reduce la necesidad de pasar información de contexto de ejecución a todos los métodos, pero combina cualquier código que use este mecanismo con el marco.

Es fácil abusar de ThreadLocal al tratar su propiedad de confinamiento de hilos como una licencia para usar variables globales o como un medio para crear argumentos de método "ocultos". Al igual que las variables globales, las variables locales de subprocesos pueden restar mérito a la reutilización e introducir acoplamientos ocultos entre las clases, por lo que deben usarse con cuidado.


Las variables de subprocesos locales se utilizan a menudo para evitar compartir en diseños basados ​​en Singletons o variables globales mutables.

Se puede usar en escenarios como hacer una conexión JDBC separada para cada subproceso cuando no está utilizando un grupo de conexiones.

private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() { public Connection initialValue() { return DriverManager.getConnection(DB_URL); } }; public static Connection getConnection() { return connectionHolder.get(); }

Cuando llame a getConnection, devolverá una conexión asociada con ese hilo. Lo mismo se puede hacer con otras propiedades como el formato de fecha, el contexto de transacción que no desea compartir entre los hilos.

También podría haber utilizado las variables locales para el mismo, pero estos recursos generalmente toman tiempo en la creación, por lo que no desea crearlos una y otra vez cada vez que realice alguna lógica de negocios con ellos. Sin embargo, los valores de ThreadLocal se almacenan en el objeto del hilo y, tan pronto como se recolecta la basura, estos valores también desaparecen.

Este link explica muy bien el uso de ThreadLocal.


Muchos marcos usan ThreadLocals para mantener algún contexto relacionado con el hilo actual. Por ejemplo, cuando la transacción actual se almacena en un ThreadLocal, no necesita pasarla como parámetro a través de cada llamada de método, en caso de que alguien de la pila necesite acceso a ella. Las aplicaciones web pueden almacenar información sobre la solicitud actual y la sesión en un ThreadLocal, de modo que la aplicación tenga fácil acceso a ellas. Con Guice puede usar ThreadLocals al implementar ámbitos personalizados para los objetos inyectados (los ámbitos de servlet predeterminados de Guice probablemente también los utilicen).

Los ThreadLocals son un tipo de variables globales (aunque un poco menos malignas porque están restringidas a un solo hilo), por lo que debe tener cuidado al usarlas para evitar efectos secundarios no deseados y pérdidas de memoria. Diseñe sus API para que los valores de ThreadLocal siempre se borren automáticamente cuando ya no sean necesarios y que no sea posible el uso incorrecto de la API (por ejemplo, así ). Los ThreadLocals se pueden usar para hacer que el código sea más limpio, y en algunos casos raros son la única forma de hacer que algo funcione (mi proyecto actual tuvo dos de estos casos; están documentados here en "Campos estáticos y variables globales").


No hay nada realmente nuevo aquí, pero hoy descubrí que ThreadLocal es muy útil cuando se utiliza la Validación de Bean en una aplicación web. Los mensajes de validación están localizados, pero de forma predeterminada usan Locale.getDefault() . Puede configurar el Validator con un MessageInterpolator diferente, pero no hay manera de especificar la configuración Locale cuando llame a validate . Por lo tanto, puede crear un ThreadLocal<Locale> estático (o, mejor aún, un contenedor general con otras cosas que debería ser ThreadLocal y luego hacer que su MessageInterpolator personalizado elija la Locale . El siguiente paso es escribir un ServletFilter que use una sesión value o request.getLocale() para seleccionar la configuración regional y almacenarla en su referencia de ThreadLocal .


ThreadLocal es una funcionalidad aprovisionada especialmente por JVM para proporcionar un espacio de almacenamiento aislado solo para subprocesos. Al igual que el valor de la instancia, la variable con ámbito está vinculada a una instancia determinada de una clase solamente. Cada objeto tiene sus únicos valores y no pueden verse entre sí. Al igual que el concepto de variables ThreadLocal, son locales para el subproceso en el sentido de las instancias de objetos. Otro subproceso, excepto el que lo creó, no puede verlo. Mira aquí

import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.IntStream; public class ThreadId { private static final AtomicInteger nextId = new AtomicInteger(1000); // Thread local variable containing each thread''s ID private static final ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> nextId.getAndIncrement()); // Returns the current thread''s unique ID, assigning it if necessary public static int get() { return threadId.get(); } public static void main(String[] args) { new Thread(() -> IntStream.range(1, 3).forEach(i -> { System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get()); })).start(); new Thread(() -> IntStream.range(1, 3).forEach(i -> { System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get()); })).start(); new Thread(() -> IntStream.range(1, 3).forEach(i -> { System.out.println(Thread.currentThread().getName() + " >> " + new ThreadId().get()); })).start(); } }


Tienes que tener mucho cuidado con el patrón ThreadLocal. Hay algunas desventajas importantes como mencionó Phil, pero una que no se mencionó es asegurarse de que el código que configura el contexto ThreadLocal no sea "re-entrante".

Pueden suceder cosas malas cuando el código que establece la información se ejecuta una segunda o tercera vez porque la información en su hilo puede comenzar a mutar cuando no lo esperaba. Así que asegúrese de asegurarse de que la información de ThreadLocal no se haya establecido antes de que la vuelva a configurar.


Un uso posible (y común) es cuando tiene un objeto que no es seguro para subprocesos, pero desea evitar la synchronizing acceso a ese objeto (lo estoy viendo, SimpleDateFormat ). En su lugar, le da a cada hilo su propia instancia del objeto.

Por ejemplo:

public class Foo { // SimpleDateFormat is not thread-safe, so give one to each thread private static final ThreadLocal<SimpleDateFormat> formatter = new ThreadLocal<SimpleDateFormat>(){ @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyyMMdd HHmm"); } }; public String formatIt(Date date) { return formatter.get().format(date); } }

Documentation


[Para referencia] ThreadLocal no puede resolver problemas de actualización de objetos compartidos. Se recomienda usar un objeto staticThreadLocal que sea compartido por todas las operaciones en el mismo hilo. Las variables ThreadLocal deben implementar el método [obligatorio] remove (), especialmente cuando se usan grupos de subprocesos en los que los subprocesos se reutilizan a menudo. De lo contrario, puede afectar la lógica de negocios posterior y causar problemas inesperados, como la pérdida de memoria.


cuando?

Cuando un objeto no es seguro para subprocesos, en lugar de una sincronización que dificulta la escalabilidad, asigne un objeto a cada subproceso y guárdelo en el alcance del subproceso, que es ThreadLocal. Uno de los objetos más utilizados, pero no seguros para subprocesos, es Conexión a la base de datos y Conexión JMS.

Cómo ?

Un ejemplo es que Spring Framework usa ThreadLocal en gran medida para administrar transacciones entre bastidores al mantener estos objetos de conexión en las variables ThreadLocal. En el nivel alto, cuando se inicia una transacción, obtiene la conexión (y desactiva la confirmación automática) y la mantiene en ThreadLocal. en las llamadas db posteriores utiliza la misma conexión para comunicarse con db. Al final, toma la conexión de ThreadLocal y confirma (o deshace) la transacción y libera la conexión.

Creo que log4j también usa ThreadLocal para mantener MDC.


La documentación lo dice muy bien: "cada subproceso que accede a [una variable local del subproceso] (a través de su método get o set) tiene su propia copia de la variable, independientemente inicializada".

Usas uno cuando cada hilo debe tener su propia copia de algo. Por defecto, los datos se comparten entre hilos.


ThreadLocal es útil cuando se desea tener un estado que no deba compartirse entre diferentes subprocesos, pero debe ser accesible desde cada subproceso durante toda su vida útil.

Como ejemplo, imagine una aplicación web, donde cada solicitud es atendida por un hilo diferente. Imagine que para cada solicitud necesita una pieza de datos varias veces, lo que es bastante costoso de calcular. Sin embargo, es posible que esos datos hayan cambiado para cada solicitud entrante, lo que significa que no puede usar una memoria caché simple. Una solución simple y rápida para este problema sería tener una variable ThreadLocal que tenga acceso a estos datos, de modo que tenga que calcularla solo una vez para cada solicitud. Por supuesto, este problema también puede resolverse sin el uso de ThreadLocal , pero lo ThreadLocal con fines ilustrativos.

Dicho esto, tenga en cuenta que los ThreadLocal s son esencialmente una forma de estado global. Como resultado, tiene muchas otras implicaciones y debe usarse solo después de considerar todas las otras soluciones posibles.


Threadlocal proporciona una manera muy fácil de lograr la reutilización de objetos sin costo.

Tuve una situación en la que varios hilos creaban una imagen de caché mutable en cada notificación de actualización.

Utilicé un Threadlocal en cada hilo, y luego cada hilo solo tendría que restablecer la imagen antigua y luego actualizarla nuevamente desde el caché en cada notificación de actualización.

Los objetos reutilizables habituales de las agrupaciones de objetos tienen un costo de seguridad de subprocesos asociado a ellos, mientras que este enfoque no tiene ninguno.


Hay 3 escenarios para usar un ayudante de clase como SimpleDateFormat en código de multiproceso, cuál es el mejor uso de ThreadLocal

Escenarios

1- Usar como compartir objeto con la ayuda de un mecanismo de bloqueo o sincronización que hace que la aplicación sea lenta

2- Usando como un objeto local dentro de un método.

En este escenario, si tenemos 4 hilos, cada uno llama a un método 1000 veces, entonces tenemos
4000 objetos SimpleDateFormat creados y en espera de que GC los borre

3- Usando ThreadLocal

Si tenemos 4 hilos y le damos a cada uno de los hilos una instancia de SimpleDateFormat
, tenemos 4 hilos , 4 objetos de SimpleDateFormat.

No hay necesidad de mecanismo de bloqueo y creación y destrucción de objetos. (Buen tiempo complejidad y espacio complejidad)


La clase ThreadLocal en Java le permite crear variables que solo pueden leerse y escribirse en el mismo hilo. Por lo tanto, incluso si dos subprocesos ejecutan el mismo código, y el código tiene una referencia a una variable ThreadLocal, entonces los dos subprocesos no pueden ver las variables ThreadLocal de cada uno.

Lee mas