Prefacio
El problema de seguridad del hilo de la lectura múltiple es sutil e inesperado, porque el orden de operaciones en múltiples lecturas es impredecible sin una sincronización adecuada. Múltiples hilos que acceden a la misma variable compartida son particularmente propensos a problemas de concurrencia, especialmente cuando múltiples subprocesos deben escribir una variable compartida, para garantizar la seguridad de los subprocesos,
En general, los usuarios deben realizar una sincronización adecuada al acceder a variables compartidas, como se muestra en la figura a continuación:
Se puede ver que la medida de sincronización generalmente es bloqueo, lo que requiere que el usuario tenga una cierta comprensión del bloqueo, lo que obviamente aumenta la carga del usuario. Entonces, ¿hay alguna manera al crear una variable, cuando cada hilo accede a él, accede a las variables de su propio hilo? De hecho, Threhoocal puede hacer esto. Tenga en cuenta que la aparición de ThreadLocal no parece resolver los problemas anteriores.
ThreadLocal se proporciona en el paquete JDK. Proporciona variables locales de hilo. Es decir, si crea una variable de ThreadLocal, entonces cada hilo que acceda a esta variable tendrá una copia local de la variable. Cuando múltiples subprocesos operan esta variable, en realidad operan las variables en su propia memoria local, evitando así los problemas de seguridad de los hilos. Después de crear una variable de hilo,
Cada hilo copiará una variable a su memoria local, como se muestra en la figura a continuación:
De acuerdo, ahora pensemos en una pregunta: el principio de implementación de ThreadLocal y ¿cómo se implementa ThreadLocal como un método de aislamiento de subprocesos variables?
Primero, debemos mirar la estructura del diagrama de clases de ThreadLocal, como se muestra en la siguiente figura:
como
Como se puede ver en el diagrama de clases anterior, hay un hilo y heredableLethreadLocals en la clase de subprocesos. Ambos tipos de variables son ThreadLocalMap, y ThreadLocalMap es un hashmap personalizado. Por defecto, ambas variables en cada hilo son nulas. Solo se crearán cuando el subproceso llame al método de conjunto de hilos de ThreadLocal o obtenga por primera vez.
De hecho, las variables locales de cada hilo no se almacenan en la instancia de ThreadLocal, sino que se almacenan en la variable de hilos de hilo del hilo de llamada. En otras palabras, las variables locales de Type ThreadLocal se almacenan en el espacio específico de memoria de subprocesos.
Threadlocal es en realidad una carcasa. Pone el valor del valor en el hilo de llamadas hilo de subprocesos a través del método establecido y lo almacena. Cuando el hilo de llamada llama a su método Get, se elimina de la variable de ThreadLocals del hilo actual. Si el hilo de llamadas no termina, la variable local se almacenará en la variable ThreadLocals del hilo de llamadas.
Por lo tanto, cuando no necesita usar variables locales, puede eliminar la variable local de la variable de pilos de hilo del hilo actual llamando al método eliminar la variable de threadlocal. Algunas personas pueden preguntar por qué Threadlocals está diseñado como una estructura de mapas. Es obvio que cada hilo puede asociarse con múltiples variables de hilo.
A continuación, podemos ingresar el código fuente en ThreadLocal como se muestra en el siguiente código:
Observa principalmente la lógica de implementación de los tres métodos establecidos, obteniendo y eliminado, de la siguiente manera:
Veamos primero el método SET (T VAR1)
Public void set (t var1) {// (1) Obtenga el hilo actual de subproceso var2 = thread.currentThread (); // (2) El hilo actual se usa como la clave para encontrar la variable de subproceso correspondiente. Si se encuentra, establezca ThreadLocal.threadLocalMap var3 = this.getMap (var2); if (var3! = null) {var3.set (this, var1); } else {// (3) La primera llamada se crea para crear el hashmap correspondiente para el hilo actual this.createMap (var2, var1); }}Como se mencionó anteriormente, el código (1), primero obtenga el hilo de llamada y luego use el hilo actual como parámetro para llamar al método getMap (VAR2). El código GetMap (Thread VAR2) es el siguiente:
ThreadLocal.threadLocalMap getMap (Thread Var1) {return var1.threadlocals; }Se puede ver que lo que hace GetMap (VAR2) es obtener las propiaslocals variables de la variable del hilo, y la variable de ThreadLocal está vinculado a la variable miembro del hilo.
Si GetMap (VAR2) devuelve no vacío, entonces establezca el valor de valor en ThreadLocals, es decir, coloque el valor de la variable actual en la variable de memoria TreveLocals del hilo actual. ThreadLocals es una estructura hashmap, donde la clave es la referencia del objeto de instancia de ThreadLocal actual, y el valor es el valor pasado a través del método establecido.
Si GetMap (VAR2) devuelve vacío, significa que el método establecido se llama la primera vez, y luego se crea la variable ThreadLocals del hilo actual. Veamos lo que se hace en CreateMap (VAR2, VAR1)?
void createMap (Thread var1, t var2) {var1.threadlocals = new ThreadLocal.threadLocalMap (this, var2); }Lo que puede ver es crear la variable ThreadLocals del hilo actual.
A continuación, veamos el método get (), el código es el siguiente:
public t get () {// (4) Obtenga el subproceso actual var1 = thread.currentThread (); // (5) Obtenga la variable ThreadLocals ThreadLocal.threadLocalMap var2 = this.getMap (var1); // (6) Si ThreadLocals no es nulo, el valor variable local correspondiente se devuelve si (var2! = Null) {threadlocal.threadlocalmap.entry var3 = var2.getentry (this); if (var3! = null) {objeto var4 = var3.value; devolver var4; }} // (7) Si ThreadLocals está vacío, se inicializa la variable de miembro de ThreadLocals del hilo actual. devolver esto.setInitialValue (); }Código (4) Primero obtenga la instancia de subproceso actual. Si la variable ThreadLocals del hilo actual no es nula, devolverá directamente la variable local del hilo actual. De lo contrario, ejecute el código (7) para inicializar, y el código de setInitialValue () es el siguiente:
Private t setInitialValue () {// (8) inicializado a objeto nulo var1 = this.initialValue (); Hilo var2 = thread.currentThread (); ThreadLocal.ThreadLocalMap var3 = this.getMap (var2); // (9) Si la variable de TreveLocals de la variable de hilo actual no está vacía si (var3! = Null) {var3.set (this, var1); // (10) Si la variable de ThreadLocals del hilo actual está vacío} else {this.createMap (var2, var1); } return var1; }Como se mencionó anteriormente, si la variable ThreadLocals del hilo actual no está vacía, entonces el valor variable local del hilo actual se establece en NULL. De lo contrario, CreateMap se llama a la variable CreatEmap del hilo actual.
A continuación, observamos el método Void Remout (), el código es el siguiente:
public void remove () {ThreadLocal.threadLocalMap var1 = this.getMap (thread.currentThread ()); if (var1! = null) {var1.remove (this); }}Como se mencionó anteriormente, si la variable de hilo de hilo del hilo actual no está vacía, se elimina la variable local especificada en la instancia de hiltlocal en el hilo actual.
A continuación, echemos un vistazo a la demostración específica, el código es el siguiente:
/*** Creado por Cong el 2018/6/3. */public class ThreadLocalTest {// (1) Función de impresión estática void imprim (String Str) {//1.1 Imprima el valor de la variable localVariable en la memoria local del hilo actual System.out.println (str + ":" + localVariable.get ()); //1.2 Borre la variable localvariable en la memoria local del hilo actual //localvariable.remove (); } // (2) Crear variable de threadlocal static threadlocal <string> localVariable = new ThreadLocal <> (); public static void main (string [] args) {// (3) Crear hilo de hilo un hilo threadone = new Thread (new runnable () {public void run () {//3.1 Establezca el valor de la variable local Variable en Thread One System.out.println ("resultado después de eliminar la variable local de hilo 1" + ":" + localVariable.get ()); // (4) Crear hilo dos hilo threadtwo = new Thread (new runnable () {public void run () {//4.1 Establezca el valor de la variable local LocalVariable en Thread un localVariable.set ("Variable local de la variable de Thread 2"); //4.2 Llame a la función de impresión imprimir ("Thread 2 ------>") "); //4.3 Impresión de la variable local. 2 " +": " + localVariable.get ());}}); // (5) Inicie el hilo ThreadOne.Start (); threadtwo.start (); }}El código (2) crea una variable de ThreadLocal;
Los códigos (3) y (4) crean hilos 1 y 2 respectivamente;
El código (5) inicia dos hilos;
El código 3.1 en el subproceso 1 establece el valor de LocalVariable a través del método establecido. Esta configuración es en realidad una copia en la memoria local de Thread 1. No se puede acceder a esta copia mediante el subproceso 2. Luego, el código 3.2 llama a la función de impresión, y el código 1.1 obtiene el valor de localvariable en la memoria local del subproceso actual (hilo 1) a través de la función get;
El hilo 2 se ejecuta similar al hilo 1.
Los resultados de la operación son los siguientes:
Aquí debemos prestar atención al problema de fuga de memoria de ThreadLocal
Cada hilo tiene una variable miembro llamada ThreadLocals dentro. El tipo de variable es hashmap. La clave es esta referencia a la variable de ThreadLocal que definimos, y el valor es el valor cuando configuramos. La variable local de cada hilo se almacena en la variable de memoria de hilo. Si el hilo actual no desaparece, estas variables locales se almacenarán hasta.
Por lo tanto, puede causar fugas de memoria, por lo que después de usarla, recuerde llamar al método de eliminación de ThreadLocal para eliminar las variables locales en TreveLocals del hilo correspondiente.
Después de desempacar los comentarios en el Código 1.2, ejecute nuevamente, y el resultado de ejecución es el siguiente:
¿Alguna vez hemos pensado en una pregunta como esta: ¿obtenemos el valor de la variable de hilo en el hilo principal en el hilo infantil?
Aquí podemos decirle que el valor de la variable de threadlocal establecido en el hilo principal no se puede obtener en el hilo infantil. Entonces, ¿hay alguna forma de hacer que el hilo infantil acceda al valor en el hilo principal? HereTablethreadlocal surgió en el futuro. HereTablethreadLocal hereda de ThreadLocal y proporciona una característica que los niños pueden acceder a variables locales establecidas en el hilo principal.
Primero, vamos al código fuente de la clase HereThablethreadLocal para leer, de la siguiente manera:
La clase pública inheritableThreadLocal <t> extiende ThreadLocal <t> {public HereTablethreadLocal () {} // (1) protegido T ChildValue (t var1) {return var1; } // (2) ThreadLocalMap getMap (Thread Var1) {return var1.inheritableThreadLocals; } // (3) void createMap (Thread var1, t var2) {var1.inheritableThreadLocals = new ThreadLocalMap (this, var2); }}Puede ver que Heritablethreadlocal hereda Threadlocal y reescribió tres métodos. El código anterior ha sido marcado. Código (3) Se puede ver que HeritableThreadLocal reescribe el método CreatEmap, por lo que se puede ver que cuando se llama el método establecido por primera vez, la instancia de la variable inhheritableThreadLocals del subproceso actual se crea, en lugar de TreveLocals.
Código (2) Puede saber que cuando se llama al método GET para obtener la variable de mapa interna del subproceso actual, se obtiene HeritableThreadLocals, no de ThreadLocals.
El punto clave está aquí, cuando se ejecuta el código reescrito (1) y cómo implementar que el hilo infantil puede acceder a las variables locales del hilo principal. Comenzando con el código creado por Thread, el constructor predeterminado de Thread y el constructor de la clase Thread.java son los siguientes:
/*** Creado por Cong el 2018/6/3. */ public Thread (Runnable Target) {init (NULL, Target, "Thread-" + NextThreadNum (), 0); } Private void init (ThreadGroup g, objetivo Runnable, nombre de cadena, Long StackSize, AccessControlContext Acc) {// ... // (4) Obtenga el subproceso actual de subproceso parent = currentTread (); // ... // (5) Si la variable de InheritableThreadLocals del hilo principal no es nulo if (parent.inheritableThreadLocals! = Null) // (6) establece inheritableThreadLocals variable en el hilo infantil this.inheritableTlocals = threadlocal.createReinheredMap (parent.inheritableReadlocals); this.stacksize = stacksize; tid = nextThreadID (); }Al crear un hilo, el método init se llamará en el constructor. Como se mencionó anteriormente, la clase HerhheritableThreadLocal Get y el método establecido opera la variable HeritableThreadLocals, por lo que la variable HereThablethreadLocal aquí no es nulo, por lo que se ejecutará el código (6). Veamos el código fuente del método CREATINHERITEDMAP, de la siguiente manera:
static threadlocalMap createInheritedMap (ThreadLocalMap ParentMap) {return New ThreadLocalMap (ParentMap); }Puede ver que CreateReRitedMap usa la variable HereTableLethreadLocals del hilo principal como el constructor para crear una nueva variable ThreadLocalMap, y luego la asigna a la variable HeritableThreadLocals del hilo infantil. Luego ingrese el constructor de ThreadLocalMap. El código fuente es el siguiente:
Private ThreadLocalMap (ThreadLocalMap ParentMap) {Entry [] ParentTable = ParentMap.Table; int len = parenttable.length; setthreshold (len); tabla = nueva entrada [Len]; for (int j = 0; j <len; j ++) {entrada e = parentTable [j]; if (e! = null) {@SupessWarnings ("sin verificar") ThreadLocal <ject> Key = (ThreadLocal <SPEt>) E.Get (); if (key! = null) {// (7) Llame al valor del objeto del método anulado = key.childValue (e.value); // return E.Value Entry C = nueva entrada (clave, valor); int h = key.threadlocalhashcode & (len - 1); while (tabla [h]! = nulo) h = nextIndex (h, len); tabla [h] = c; tamaño ++; }}}}}}Lo que hace el código anterior es copiar el valor de la variable Miembro del miembro del hilo principal del hilo principal al nuevo objeto ThreadLocalMap, y el código (1) reescritado por el código (7) HeritableLethreadLocal Class también entra.
En general: la clase HereThablethreadLocal reescribe el código (2) y (3) y guarda las variables locales en la variable HereTablethreadLocals del hilo específico. Cuando el hilo establece la variable a través del método SET o GET de la instancia de HeritableBhethreadLocals, creará la variable HereTableBlethreadLocals del hilo actual. Cuando el hilo principal crea el hilo del niño,
El constructor copiará la variable local en la variable HereThablethreadLocals en el hilo principal y la copiará en la variable HeritableLethreadLocals del hilo infantil.
Después de que el principio se entiende bien, tomemos un ejemplo para verificar lo que sabemos anteriormente, de la siguiente manera:
paquete com.hjc;/*** creado por Cong el 2018/6/3. */public class inheritableLetReadLocalTest {// (1) Crear variable de hilo Public Static ThreadLocal <String> ThreadLocal = new ThreadLocal <String> (); public static void main (string [] args) {// (2) establecer la variable de hilo threadlocal.set ("hola java"); // (3) Iniciar hilo de hilo infantil hilo de hilo = nuevo hilo (new runnable () {public void run () {// (4) El valor del hilo infantil sale de la variable de hilo System.out.println ("subthread:" + threadlocal.get ());}}); Thread.Start (); // (5) El subproceso principal emite el valor variable de subproceso System.out.println ("PROPE HIRED:" + ThreadLocal.get ()); }}Los resultados de la operación son los siguientes:
Es decir, después de que se establece la misma variable de hilo en el hilo principal, no se puede obtener en el hilo infantil. Según la introducción en la sección anterior, este debería ser un fenómeno normal, porque el hilo actual es un hilo infantil cuando el hilo infantil llama al método GET, y el método establecido se llama para establecer la variable de subproceso es el hilo principal. Los dos son hilos diferentes, y naturalmente, el hilo infantil devuelve nulo al acceder.
Entonces, ¿hay alguna forma de hacer que el hilo infantil acceda al valor en el hilo principal? La respuesta es sí, así que use el heredero de LeaTlocal analizado por nuestro principio anterior.
Modifique el código (1) del ejemplo anterior a:
// (1) Crear variable de hilo public Public Static ThreadLocal <String> ThreadLocal = new HereTablethEltLocal <String> ();
Los resultados de la operación son los siguientes:
Se puede ver que el valor variable de hilo ahora se puede obtener normalmente del hilo infantil. Entonces, ¿en qué circunstancias necesitan los niños para obtener variables de hilo del hilo principal?
Hay muchas situaciones, como la variable ThreadLocal que almacena la información de inicio de sesión del usuario. Es muy probable que las subcontrataciones también necesiten usar información de inicio de sesión del usuario. Por ejemplo, algunos middleware deben usar una ID de seguimiento unificada para grabar todo el enlace de llamadas.
Uso de ThreadLocal en la solicitud de resorte Alcance del alcance de la frijol
Sabemos que al configurar un frijol en XML en Spring, puede especificar el atributo de alcance para configurar el alcance del bean para ser singleton, prototipo, solicitud, sesión, etc. El principio de implementación del alcance del bean se implementa utilizando ThreadLocal. Si desea un frijol en su contenedor de primavera para tener algún alcance de la web,
Además de los atributos de alcance correspondientes requeridos para configurar el nivel de frijol, también debe configurarse en Web.xml de la siguiente manera:
<Oyerer> <Oyerer-class> org.springframework.web.context.request.requestContextListener </oyequer-class> </oyente>
Aquí observamos principalmente dos métodos de requestContextListener:
Public nsoid requestInitialized (ServLetRequestEvent SolicitEvent)
y
Public void requestDestroyed (ServLetRequestEvent SolicitEvent)
Cuando llegue una solicitud web, se ejecutará el método Solicitializado:
public void requestInitialized (ServLetRequestEvent requestEvent) {..... omitir httpservletRequest request = (httpservletRequest) request.getServletRequest (); ServLetRequestAttributes Attributes = new ServLetRequestAttributes (solicitud); request.setAttribute (request_attributes_attribute, atributes); LocaleContexTholder.SetLocale (request.getLocale ()); // Establezca el atributo a ThreadLocal Variable requestContexTholder.SetRequestatTributes (atributos); } public static void setRequestatTributes (requestattributes atributes) {setRequestatTributes (atributes, false); } public static void setRequestattributes (requestattributes atributes, boolean heredable) {if (attributes == null) {resetrequestatTributes (); } else {// predeterminado heredable = false if (heredable) {hereTeTableRequestatTributesSholder.set (atributos); requestAttributesholder.remove (); } else {requestTributesSholder.set (atributos); HereTableSequestatTributesholder.remove (); }}}Puede ver el código fuente anterior. Dado que el heredable predeterminado es falso, nuestros valores de atributos se colocan en requestattributeshoder, y su definición es:
Private Static Final ThreadLocal <SquitleAttributes> requestTributesSholder = new NamedThreadLocal <SquitingAttributes> ("Atributos de solicitud"); Private static final ThreadLocal <RequestAttributes> HereThableSequestAtTributesSholder = new NamedEnhereTablethreadLocal <SquitleAttributes> ("Solicitar contexto");Entre ellos, Namedthreadlocal <t> extiende ThreadLocal <t>, por lo que no es heredable.
Entre ellos, Namedthreadlocal <t> extiende ThreadLocal <t>, por lo que no es heredable.
Nameinheritablethreadlocal <t> extiende heredableThreadLocal <t>, por lo que tiene herencia, por lo que el valor de atributo colocado en el requestContextholder de forma predeterminada no se puede obtener en el hilo infantil.
Cuando finaliza la solicitud, se llama al método de requisito y el código fuente es el siguiente:
public void requestDestroyed (ServLetRequestEvent RequestEvent) {ServLetRequestTributes atributes = (ServLetRequestTributes) request.getServletRequest (). getAttribute (request_attributes_attribute); ServLetRequestatTributes ThreadTributes = (ServLetRequestatTributes) requestContexTholder.getRequestatTributes (); if (threadtibutes! = null) {// Es muy probable que borremos el hilo del hilo actual en el hilo de solicitud inicial if (attributes == null) {atributes = threadTtributes; } // Cuando termine la solicitud, borre la variable de subproceso del hilo actual. LocaleContexTholder.resetLocaleContext (); RequestContexTholder.ResetRequestTributes (); } if (attributes! = null) {attributes.requestCompleted (); }}A continuación, echemos un vistazo a la lógica de la solicitud web de llamadas desde la tabla de sincronización:
Es decir, cada vez que se inicia una solicitud web antes de procesar el contexto (aplicación específica) en TomCat, la propiedad SoldContexTholder se establecerá después de que el host coincida, para que el requesttributesholder no esté vacío y se borrará al final de la solicitud.
Por lo tanto, de manera predeterminada, no se puede acceder a los hilos de atributo infantiles colocados en el requestContexTholder, y los frijoles en el alcance de la solicitud de Spring se implementan utilizando ThreadLocal.
A continuación, se realiza una solicitud de simulación de ejemplo, el código es el siguiente:
La configuración web.xml es la siguiente:
Debido a que es un alcance de solicitud, debe ser un proyecto web y el requestContextListener debe configurarse en web.xml.
<Oyerer> <Oyerer-class> org.springframework.web.context.request.requestContextListener </oyequer-class> </oyente>
Luego inyecte una solicitud de margen en el contenedor del COI. El código es el siguiente:
<bean id = "requestbean" scope = "request"> <Property name = "name" value = "hjc" /> <aop: Scoped-Proxy /> </le bean>
El código de prueba es el siguiente:
@WebResource ("/testService") clase pública TestRpc {@aUtoWired SolicitBean requestInfo; @ResourCeMapping ("Test") Public ActionResult Test (ErrorContext Context) {ActionResult result = new ActionResult (); pvginfo.setName ("hjc"); Name de cadena = requestInfo.getName (); resultado.setValue (nombre); resultado de retorno; }}Como se mencionó anteriormente, primero configure el requestContextListener en Web.xml, luego inyecte la instancia de requestbean en el contenedor IOC con el alcance de la solicitud. Finalmente, la instancia de requestbean se inyecta en TestRPC. La prueba del método primero llama al Método de requestinfo setname para establecer el atributo de nombre, luego obtener el atributo de nombre y devolver.
Aquí, si el objeto RequestInfo es Singleton, después de que múltiples subprocesos llamen al método de prueba al mismo tiempo, cada subproceso es una operación de conjunto. Esta operación no es atómica y causará problemas de seguridad de hilos. El alcance declarado aquí es el nivel de solicitud, y cada hilo tiene una variable local con requestInfo.
La tabla de sincronización de la solicitud de método de ejemplo anterior es la siguiente:
Necesitamos centrarnos en lo que sucede al llamar a la prueba:
De hecho, el requestinfo creado anteriormente es después de ser proxyed por CGLIB (si está interesado, puede estudiar ScopedProxyFactoryBean y otros tipos), por lo que cuando llame a Setname o GetName, será interceptado por DynamicAdVised Interceptor. El interceptor eventualmente llamará al método GET de SoldScope para obtener las variables locales en poder del hilo actual.
La clave está aquí. Necesitamos observar el código fuente del método SolicScope Get de la siguiente manera:
Public Object get (String Name, ObjectFactory ObjectFactory) {requestAttributes atributes = requestContexTholder.CurrentRequestatTributes (); // (1) Object ScopeDoBject = Attributes.getAttribute (name, getSCope ()); if (scopedObject == null) {scopedObject = objectFactory.getObject (); // (2) atributes.SetAttribute (name, ScopedObject, getScope ()); // (3)} return ScopedObject; }Se puede ver que cuando se inicia una solicitud, el requesttributesholder se establecerá llamando a requestContextListener.Requestinitialized en requestContextListener.setRequestatTributess.
Luego, después de enrutar la solicitud al método de prueba de TestRPC, la primera vez que el método SetName se llama en el método de prueba, el método requestscope.get () eventualmente se llamará. El código en el método GET (1) obtiene el valor del conjunto de atributos guardado por la variable de hilo Local RequestTributSholder establecido a través de requestContextListener.RequestInitialized.
Luego verifique si hay un atributo llamado RequestInfo en el conjunto de atributos. Dado que es la primera llamada, no existe, por lo que el código se ejecutará (2) y dejará que Spring cree un objeto RequestInfo, y luego lo establecerá en los atributos de conjunto de atributos, es decir, se guardará en la memoria local del subproceso de solicitud actual. Luego devuelva el objeto creado y llame al nombre de nombre del objeto creado.
Finalmente, el método GetName se llama en el método de prueba, y se llamará al método requestscope.get (). El código en el método GET (1) obtiene el hilo local requestTibutes de requisitos de requisitos establecidos a través de requestContextListener.Requestinitialized, y luego vea si hay un atributo llamado requestInfo en el conjunto de atributos.
Dado que el Bean llamado RequestInfo se ha establecido en la variable ThreadLocal cuando se llama para SetName por primera vez, y se llama al mismo hilo para SetName y GetName, el objeto RequestInfo creado al llamar a SetName se devuelve directamente aquí, y luego se llama a su método GetName.
Hasta ahora, hemos entendido el principio de implementación de ThreadLocal y señalamos que ThreadLocal no admite la herencia; Luego explicamos inmediatamente cómo Heritablethreadlocal compensa la característica de que ThreadLocal no admite la herencia; Finalmente, introdujimos brevemente cómo usar ThreadLocal en el marco de Spring para implementar el alcance Bean of Reqeust.
Resumir
Lo anterior es todo el contenido de este artículo. Espero que el contenido de este artículo tenga cierto valor de referencia para el estudio o el trabajo de todos. Si tiene alguna pregunta, puede dejar un mensaje para comunicarse. Gracias por su apoyo a Wulin.com.