Recientemente, leí el código de capa de Android Framework y vi la clase ThreadLocal. Estaba un poco desconocido, así que leí varios blogs relacionados uno por uno. Luego estudié el código fuente y descubrí que mi comprensión era diferente de las publicaciones de blog que leí antes, así que decidí escribir un artículo para hablar sobre mi comprensión, con la esperanza de que pueda desempeñar el siguiente papel:
- Puede borrar los resultados de la investigación y profundizar su comprensión;
- Puede desempeñar un papel en atraer a Jade y ayudar a los estudiantes interesados a aclarar sus ideas;
- Comparta su experiencia de aprendizaje y comuníquese y aprenda con todos.
1. ¿Qué es Threadlocal?
ThreadLocal es la clase básica de la biblioteca de clase Java, debajo del paquete java.lang;
La explicación oficial es la siguiente:
Implementa un almacenamiento local de subprocesos, es decir, una variable para la cual cada hilo tiene su propio valor. Todos los hilos comparten el mismo objeto de hilo, pero cada uno ve un valor diferente al acceder a él, y los cambios realizados por un hilo no afectan los otros hilos. La implementación admite valores nulos.
El significado general es:
Se puede implementar el mecanismo de almacenamiento local de los subprocesos. La variable ThreadLocal es una variable que puede tener diferentes valores en diferentes hilos. Todos los hilos pueden compartir el mismo objeto de hilo, pero diferentes hilos pueden obtener diferentes valores cuando se accede, y los cambios de cualquier hilo no afectarán otros hilos. Las implementaciones de clase admiten valores nulos (se pueden pasar y acceder a los valores nulos en los métodos establecidos y get).
En resumen, hay tres características:
- Obtenga diferentes valores cuando se accede por diferentes hilos
- Cualquier cambio de hilo no afectará otros hilos
- Soporte NULL
Los siguientes son ejemplos de estas características. Primero, definimos una clase de prueba. En esta clase, verificamos las tres características mencionadas anteriormente. La definición de clase es la siguiente:
Test.java
Prueba de clase pública {// Definir el nombre de ThreadLocal de ThreadLocal static privado de ThreadLocal; public static void main (string [] args) lanza la excepción {name = new ThreadLocal (); // Definir Thread Athread a = new Thread () {public void run () {System.Println ("Antes de invocar set, valor es:"+name.get ()); name.set ("hilo de hilo (" hilo de hilo. A "); System.out.println (" After Invoke Set, Value es: "+name.get ());}}; // Define Thread bthread b = new thread () {public void run () {System.out.println (" antes de Invoke Set, Value es: "+name.get ()); name.set (" Thread B "); System.out.out.pintln (" Avista, "Name.get ()); Name.set (" Threat B "); System.out.out.Println (" Invoke Set, "Name.get ()); Name.set (" Threat B "); System.out.out.Println (" Invoke Set, "Name.get ()); Name.set (" Thread B "); : "+name.get ());}}; // no invocar establecido, imprima el valor es nullsystem.out.println (name.get ()); // Invoke set para completar un valueName.set (" Thread Main ");// Iniciar el hilo aa.start (); a.join () ;//pimpa el valor después del valor de hilo por hilo asystem.println (name.); Bb.start (); B.Join (); // Imprima el valor después de cambiar el valor de Thread Bsystem.out.println (name.get ())}}Análisis de código:
Desde la definición podemos ver que solo se declara un objeto de hilo y los otros tres hilos (hilo principal, hilo A y hilo b) comparten el mismo objeto; Luego, el valor del objeto se modifica en diferentes hilos y se accede al valor del objeto en diferentes subprocesos, y el resultado se sale a ver en la consola.
Ver los resultados:
De los resultados de la salida de la consola, puede ver que hay tres salidas nulas. Esto se debe a que el objeto no se asignó antes de la salida, lo que verificó la característica de soporte nulo. Además, se puede encontrar que en cada hilo, he modificado el valor del objeto, pero cuando otros subprocesos acceden al objeto, no es el valor modificado, sino el valor local de hilo; Esto también verifica las otras dos características.
2. El papel de Threadlocal
Todos saben que sus escenarios de uso son en su mayoría programación de múltiples subprocesos. En cuanto a su función específica, ¿cómo se dice eso? Creo que esto solo se puede definir de manera general, porque los atributos funcionales de una cosa limitarán el pensamiento de todos después de que se define. Por ejemplo, los cuchillos de cocina se usan para cortar verduras, y muchas personas no lo usarán para cortar sandías.
Aquí, hablaré sobre mi comprensión de su función solo de referencia y espero que sea útil. Describámoslo de esta manera. Cuando un programa multiprochado necesita encapsular algunas tareas de la mayoría de los hilos (es decir, algún código en el método Ejecutar), ThreadLocal se puede usar para envolver las variables miembros relacionadas con los subprocesos en el cuerpo de encapsulación para garantizar la exclusividad del acceso a los subprocesos, y todos los subprocesos pueden compartir un objeto de encapsulación; Puede referirse a Looper en Android. Los programadores que no pueden describir problemas con el código no son buenos programadores;
Mire el código: una herramienta para indicar el código que consumen mucho tiempo de un hilo (creado para ilustrar el problema)
StatisticCostTime.java
// Clase que estadística la clase de TimEpublic de costo STATISTICSCOSTTIME {// Registre el tiempo de inicio // private ThreadLocal Starttime = new ThreadLocal (); private Long Starttime; // private ThreadLocal Costtime = new ThreadLocal (); private Long CossTime; private StatisticCostTime () {} // SingletOnpublic estaticación final estadística final estadística final static class instanceFactory {private static final StatisticCostTime instance = new StatisticCostTime ();} // startPublic Void start () {// starttime.set (sistema. nanotime ()); starttime = system.nanotime ();} // endpublic void end () {// centtime.set (system. nanotime () - starttime.get ());)); System.nanotime () - starttime;} public Long getStarttime () {return starttime; // return starttime.get ();} public Long getCostTime () {// return Costtime.get (); return costtime;}Bien, el diseño de la herramienta se completa, ahora lo usamos para contar los hilos que requieren mucho tiempo y prueben:
Main.java
public class Main {public static void main (string [] args) lanza la excepción {// Defina el hilo athread a = new Thread () {public void run () {try {// Iniciar registro de registro TimestatisticCostTime.shareInstance (). Start (); Sleep (200); // Imprimir el tiempo de inicio de A a A System.out.println ("a-starttime:"+statisticCostTime.shareInstance (). e) {}}}; // iniciar aa.start (); // define el hilo bthread b = new thread () {public void run () {try {// registra la hora de inicio de b1statisticCostTime.shareinstance (). Start (); sleep (100); // Imprimir el tiempo de inicio hasta ConsolySystem.out.println ("B1-starttime:"+StatisticCostTime.ShareInstance (). GetStartTime ()); // Finalizar la hora de inicio de B1StatisticCostTime.ShareInstance (). End (); // El tiempo de costo del tiempo de costo del tiempo de costo de B1system.out.println ("b1:"+statisticCostTime.shareInstance (). GetCostTime ()); // Inicie el tiempo de registro de B2StatisticCostTime.ShareInstance (). Start (); Sleep (100); // Impresión de tiempo de inicio del tiempo de inicio de imprimación de B2system.out.println ("B2-starttime:"+statisticCostTime.shareInstance (). GetStartTime ()); // Tiempo de registro final de B2StatisticCostTime.ShareInstance (). End (); // Impresión Tiempo de costo de B2System.out.println ("B2:"+StatisticCostTime.ShareInstance ().Después de ejecutar el código, el resultado de la salida es el siguiente: la precisión del resultado de la salida son los nanosegundos.
Depende de si el resultado es diferente de lo que esperábamos. Descubrí que el resultado de A debería ser aproximadamente igual a B1+B2. ¿Cómo es que se convierte en lo mismo que B2? La respuesta es que cuando definimos las variables de tiempo de inicio y tiempo de costo, la intención original no debe compartirse, pero debe ser exclusiva del hilo. Aquí las variables se comparten con el singleton, por lo que al calcular el valor de A, la hora de inicio realmente ha sido modificada por B2, por lo que se emite el mismo resultado que B2.
Ahora abramos la parte comentada en StatisticCostTime y lo intentemos cambiándolo al método de declaración de ThreadLocal.
Ver los resultados:
¡Ah! Esto logró el efecto esperado. En este momento, algunos estudiantes dirían que esto no es el acceso a la corriente de subprocesos, y ¿puedo garantizar la seguridad de los subprocesos siempre que use ThreadLocal? ¡La respuesta es no! En primer lugar, podemos averiguar por qué hay problemas de seguridad de hilos, pero solo hay dos situaciones:
1. Ha compartido recursos que no deben compartirse entre los hilos;
2. No garantiza el acceso ordenado a los recursos compartidos entre hilos;
El primero se puede resolver mediante el método "tiempo de intercambio espacial", utilizando ThreadLocal (también puede declarar directamente las variables locales de Thread), y el segundo puede resolverse mediante el método de "tiempo de intercambio espacial", que obviamente no es lo que puede hacer Threadlocal.
3. Principio de threadlocal
El principio de implementación es en realidad muy simple. Cada vez que la operación de lectura y escritura al objeto ThreadLocal es en realidad una operación de lectura y escritura al objeto Valores del hilo; Aquí aclaramos que no hay creación de una copia de la variable, porque el espacio de memoria asignado por la variable no se usa para almacenar el objeto t, pero los valores del hilo se utilizan para almacenar el objeto t; Cada vez que llamamos al método de conjunto de hilo en el hilo, en realidad es un proceso de escribir el objeto al objeto de valores correspondientes del hilo; Al llamar al método GET de ThreadLocal, en realidad es un proceso de obtener el objeto del objeto de valores correspondientes del hilo.
Ver el código fuente:
TRUCHLOCAL MIEMBER Variable Conjunto
/*** Establece el valor de esta variable para el hilo actual. Si se establece en * {@code null}, el valor se establecerá en NULL y la entrada subyacente aún estará presente. * * @param Valor El nuevo valor de la variable para el hilo de llamadas. */public void set (t value) {Thread currentThread = Thread.CurrentThread (); Valores valores = valores (currentThread); if (valores == null) {valores = inicializeValues (currentThread); } valores.put (this, valor);}Método de miembro de Treadlocal Get
/*** Devuelve el valor de esta variable para el hilo actual. Si una entrada * aún no existe para esta variable en este hilo, este método * creará una entrada, poblando el valor con el resultado de * {@link #InitialValue ()}. * * @return El valor actual de la variable para el hilo llamado. */@SupressWarnings ("sin verificar") public t get () {// optimizado para la ruta rápida. Thread CurrentThread = Thread.CurrentThread (); Valores valores = valores (currentThread); if (valores! = null) {objeto [] table = valores.table; int index = hash & valores.mask; if (this.reference == table [index]) {return (t) tabla [índice + 1]; }} else {valores = inicializeValues (currentThread); } return (t) valores.getAfterMiss (this);}ThreadLocal Miember Method InitializeValues
/*** Crea instancia de valores para este hilo y tipo de variable. */Valores inicializeValues (corriente de hilo) {return current.localValues = new Values ();}Valores de método de miembro de ThreadLocal
/*** Obtiene la instancia de valores para este hilo y tipo de variable. */Valores valores (corriente de hilo) {return current.localValues;}Entonces, ¿cómo lees y escribes objetos en estos valores?
Los valores existen como una clase interna de ThreadLocal; Estos valores incluyen un objeto de matriz importante [], que es la parte clave de responder la pregunta. Se utiliza para almacenar varios tipos de variables de Treadlocal en el hilo. Entonces, la pregunta es, ¿cómo se asegura de no obtener valores de otros tipos al tomar variables de cierto tipo? En general, un mapa se asignará de acuerdo con el valor clave; Sí, la idea es esta idea, pero no se implementa con el mapa aquí, es un mecanismo de mapa implementado con un objeto []; Sin embargo, si desea usar MAP para comprenderlo, no es posible porque el mecanismo es el mismo; La clave en realidad corresponde a la referencia débil de ThreadLocal, y el valor corresponde al objeto en el que pasamos.
Expliquemos cómo usar el objeto [] para implementar el mecanismo de mapa (consulte la Figura 1); Utiliza la paridad del subíndice de matriz para distinguir la clave y el valor, es decir, la tabla a continuación almacena la clave en la posición uniforme y el número impar almacena el valor. Así es como se hace. Si los estudiantes interesados quieren conocer la implementación del algoritmo, pueden estudiarlo en profundidad, no lo explicaré en detalle aquí.
Según el primer ejemplo anterior, se analiza la situación de almacenamiento:
Cuando se ejecuta el programa, hay tres hilos A, B y Main. Cuando name.set () se llama en el hilo, se asignan tres espacios de memoria idénticos en el área de almacenamiento de almacenamiento para tres instancias de hilo al mismo tiempo para almacenar el objeto de valores, utilizando la referencia de nombre como clave, y el objeto específico se almacena como el valor en tres objetos diferentes [] (ver la figura a continuación):
4. Resumen
ThreadLocal no puede resolver completamente el problema de concurrencia durante la programación de múltiples subprocesos. Este problema también requiere diferentes soluciones para elegir de acuerdo con diferentes situaciones, "espacio para el tiempo" o "tiempo para el espacio".
La función más grande de ThreadLocal es convertir las variables compartidas de subprocesos en variables locales de subprocesos para lograr el aislamiento entre los hilos.
Lo anterior se trata de comprender rápidamente ThreadLocal en Java. Espero que sea útil para todos. Si hay alguna deficiencia, deje un mensaje para señalarlo. Gracias amigos por su apoyo para este sitio.