Preface
The thread safety problem of multithreading is subtle and unexpected, because the order of operations in multithreads is unpredictable without proper synchronization. Multiple threads accessing the same shared variable is particularly prone to concurrency problems, especially when multiple threads need to write a shared variable, in order to ensure thread safety,
Generally, users need to perform appropriate synchronization when accessing shared variables, as shown in the figure below:
It can be seen that the synchronization measure is generally locking, which requires the user to have a certain understanding of the lock, which obviously increases the burden on the user. So is there a way when creating a variable, when each thread accesses it, it accesses its own thread's variables? In fact, ThreaLocal can do this. Note that the emergence of ThreadLocal does not appear to solve the above problems.
ThreadLocal is provided in the JDK package. It provides thread-local variables. That is, if you create a ThreadLocal variable, then each thread accessing this variable will have a local copy of the variable. When multiple threads operate this variable, they actually operate the variables in their own local memory, thereby avoiding thread safety issues. After creating a ThreadLocal variable,
Each thread will copy a variable to its local memory, as shown in the figure below:
Okay, now let’s think about a question: the implementation principle of ThreadLocal, and how is ThreadLocal implemented as a variable thread isolation method, internally?
First, we need to look at the class diagram structure of ThreadLocal, as shown in the following figure:
like
As can be seen in the above class diagram, there is a threadLocals and inheritableThreadLocals in the Thread class. Both types of variables are ThreadLocalMap, and ThreadLocalMap is a customized Hashmap. By default, both variables in each thread are null. They will only be created when the thread calls the ThreadLocal set or get method for the first time.
In fact, the local variables of each thread are not stored in the ThreadLocal instance, but are stored in the threadLocals variable of the calling thread. In other words, local variables of type ThreadLocal are stored in the specific thread memory space.
ThreadLocal is actually a shell. It puts the value value into the calling thread thread threadLocals through the set method and stores it. When the calling thread calls its get method, it is taken out from the threadLocals variable of the current thread. If the calling thread does not terminate, then the local variable will be stored in the threadLocals variable of the calling thread.
Therefore, when you do not need to use local variables, you can delete the local variable from the threadLocals variable of the current thread by calling the remove method of the ThreadLocal variable. Some people may ask why threadLocals is designed as a Map structure? It is obvious that each thread can be associated with multiple ThreadLocal variables.
Next we can enter the source code in ThreadLocal as shown in the following code:
It mainly looks at the implementation logic of the three methods set, get, and remove, as follows:
Let's look at the set(T var1) method first
public void set(T var1) { //(1) Get the current thread Thread var2 = Thread.currentThread(); //(2) The current thread is used as the key to find the corresponding thread variable. If found, set ThreadLocal.ThreadLocalMap var3 = this.getMap(var2); if(var3 != null) { var3.set(this, var1); } else { //(3) The first call is created to create the corresponding Hashmap for the current thread this.createMap(var2, var1); } }As mentioned above code (1), first get the calling thread, and then use the current thread as a parameter to call the getMap(var2) method. The getMap(Thread var2) code is as follows:
ThreadLocal.ThreadLocalMap getMap(Thread var1) { return var1.threadLocals; }It can be seen that what getMap(var2) does is to get the thread's own variable threadLocals, and the threadlocal variable is bound to the thread's member variable.
If getMap(var2) returns not empty, then set the value value to threadLocals, that is, put the current variable value into the memory variable threadLocals of the current thread. threadLocals is a HashMap structure, where key is the reference of the current ThreadLocal instance object, and value is the value passed through the set method.
If getMap(var2) returns empty, it means that the set method is called the first time, and then the threadLocals variable of the current thread is created. Let's see what is done in createMap(var2, var1)?
void createMap(Thread var1, T var2) { var1.threadLocals = new ThreadLocal.ThreadLocalMap(this, var2); }What you can see is to create the threadLocals variable of the current thread.
Next, let's look at the get() method, the code is as follows:
public T get() { //(4) Get the current thread Thread var1 = Thread.currentThread(); //(5) Get the threadLocals variable ThreadLocal.ThreadLocalMap var2 = this.getMap(var1); //(6) If threadLocals is not null, the corresponding local variable value is returned if(var2 != null) { ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this); if(var3 != null) { Object var4 = var3.value; return var4; } } //(7) If threadLocals is empty, the threadLocals member variable of the current thread is initialized. return this.setInitialValue(); }Code (4) First get the current thread instance. If the threadLocals variable of the current thread is not null, it will directly return the local variable of the current thread. Otherwise, execute code (7) to initialize, and the code of setInitialValue() is as follows:
private T setInitialValue() { //(8) Initialized to null Object var1 = this.initialValue(); Thread var2 = Thread.currentThread(); ThreadLocal.ThreadLocalMap var3 = this.getMap(var2); //(9) If the threadLocals variable of the current thread variable is not empty if(var3 != null) { var3.set(this, var1); //(10) If the threadLocals variable of the current thread is empty} else { this.createMap(var2, var1); } return var1; }As mentioned above, if the threadLocals variable of the current thread is not empty, then the local variable value of the current thread is set to null. Otherwise, createMap is called to createMap variable of the current thread.
Next, we look at the void remove() method, the code is as follows:
public void remove() { ThreadLocal.ThreadLocalMap var1 = this.getMap(Thread.currentThread()); if(var1 != null) { var1.remove(this); } }As mentioned above, if the threadLocals variable of the current thread is not empty, the local variable specified in the ThreadLocal instance in the current thread is deleted.
Next, let's take a look at the specific demo, the code is as follows:
/** * Created by cong on 2018/6/3. */public class ThreadLocalTest { //(1) Print function static void print(String str) { //1.1 Print the value of the localVariable variable in the local memory of the current thread System.out.println(str + ":" + localVariable.get()); //1.2 Clear the localVariable variable in the local memory of the current thread //localVariable.remove(); } //(2) Create ThreadLocal variable static ThreadLocal<String> localVariable = new ThreadLocal<>(); public static void main(String[] args) { //(3) Create thread one Thread threadOne = new Thread(new Runnable() { public void run() { //3.1 Set the value of local variable localVariable in thread one localVariable.set("Local variable of thread 1"); //3.2 Call the print function print("Thread1----->"); //3.3 Print the local variable value System.out.println("Result after removing the local variable of thread 1" + ":" + localVariable.get()); } }); //(4) Create thread two Thread threadTwo = new Thread(new Runnable() { public void run() { //4.1 Set the value of local variable localVariable in thread one localVariable.set("Local variable of thread 2"); //4.2 Call the print function print("Thread 2----->"); //4.3 Print the local variable value System.out.println("Result after removing the local variable of thread 2" + ":" + localVariable.get()); } }); //(5) Start the thread threadOne.start(); threadTwo.start(); }}Code (2) creates a ThreadLocal variable;
Codes (3) and (4) create threads 1 and 2 respectively;
Code (5) Starts two threads;
Code 3.1 in thread 1 sets the value of localVariable through the set method. This setting is actually a copy in thread 1's local memory. This copy cannot be accessed by thread 2. Then code 3.2 calls the print function, and code 1.1 obtains the value of localVariable in the local memory of the current thread (thread 1) through the get function;
Thread 2 executes similar to Thread 1.
The operation results are as follows:
Here we need to pay attention to the memory leak problem of ThreadLocal
Each thread has a member variable named threadLocals inside. The variable type is HashMap. The key is this reference to the ThreadLocal variable we defined, and the value is the value when we set. The local variable of each thread is stored in the thread's own memory variable threadLocals. If the current thread does not disappear, then these local variables will be stored until.
Therefore, it may cause memory leakage, so after using it, remember to call the remove method of ThreadLocal to delete the local variables in threadLocals of the corresponding thread.
After unpacking the comments in code 1.2, run again, and the run result is as follows:
Have we ever thought about a question like this: Do we get the value of the ThreadLocal variable set in the parent thread in the child thread?
Here we can tell you that the value of the ThreadLocal variable set in the parent thread cannot be obtained in the child thread. So is there a way to get the child thread to access the value in the parent thread? InheritableThreadLocal came into being in the future. InheritableThreadLocal inherits from ThreadLocal and provides a feature that children can access local variables set in the parent thread.
First, let’s go to the source code of the InheritableThreadLocal class to read, as follows:
public class InheritableThreadLocal<T> extends ThreadLocal<T> { public InheritableThreadLocal() { } // (1) protected 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); }}You can see that InheritableThreadlocal inherits ThreadLocal and rewritten three methods. The code above has been marked. Code (3) It can be seen that InheritableThreadLocal rewrites the createMap method, so it can be seen that when the set method is called for the first time, the instance of the inheritableThreadLocals variable of the current thread is created, rather than threadLocals.
Code (2) You can know that when the get method is called to obtain the internal map variable of the current thread, the inheritableThreadLocals is obtained, not threadLocals.
The key point is here, when the rewritten code (1) is executed, and how to implement that the child thread can access the parent thread's local variables. Starting with the code created by Thread, the default constructor of Thread and the constructor of Thread.java class are as follows:
/** * Created by cong on 2018/6/3. */ public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { //... //(4) Get the current thread Thread parent = currentThread(); //... //(5) If the inheritableThreadLocals variable of the parent thread is not null if (parent.inheritableThreadLocals != null) //(6) Set inheritableThreadLocals variable in the child thread this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); this.stackSize = stackSize; tid = nextThreadID(); }When creating a thread, the init method will be called in the constructor. As mentioned earlier, the inheritableThreadLocal class get and the set method operates the variable inheritableThreadLocals, so the inheritableThreadLocal variable here is not null, so the code (6) will be executed. Let's see the source code of the createInheritedMap method, as follows:
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); }You can see that createInheritedMap uses the inheritableThreadLocals variable of the parent thread as the constructor to create a new ThreadLocalMap variable, and then assigns it to the inheritableThreadLocals variable of the child thread. Then enter the constructor of the ThreadLocalMap. The source code is as follows:
private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { //(7) Call the overridden method Object value = key.childValue(e.value);//Return e.value Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } } } }What the above code does is copy the value of the parent thread's inheritableThreadLocals member variable to the new ThreadLocalMap object, and the code (1) rewritten by the code (7) InheritableThreadLocal class also comes into view.
In general: the InheritableThreadLocal class rewrites the code (2) and (3) and saves the local variables into the inheritableThreadLocals variable of the specific thread. When the thread sets the variable through the set or get method of the InheritableThreadLocals instance, it will create the inheritableThreadLocals variable of the current thread. When the parent thread creates the child thread,
The constructor will copy the local variable in the inheritableThreadLocals variable in the parent thread and copy it into the inheritableThreadLocals variable of the child thread.
After the principle is well understood, let’s take an example to verify what we know above, as follows:
package com.hjc;/** * Created by cong on 2018/6/3. */public class InheritableThreadLocalTest { //(1) Create thread variable public static ThreadLocal<String> threadLocal = new ThreadLocal<String>(); public static void main(String[] args) { //(2) Set thread variable threadLocal.set("hello Java"); //(3) Start child thread Thread thread = new Thread(new Runnable() { public void run() { //(4) The value of the child thread outputs the thread variable System.out.println("Subthread:" + threadLocal.get()); } }); thread.start(); //(5) The main thread outputs the thread variable value System.out.println("Parent thread:" + threadLocal.get()); }}The operation results are as follows:
That is to say, after the same ThreadLocal variable is set in the parent thread, it cannot be obtained in the child thread. According to the introduction in the previous section, this should be a normal phenomenon, because the current thread is a child thread when the child thread calls the get method, and the set method is called to set the thread variable is the main thread. The two are different threads, and naturally the child thread returns null when accessing.
So is there a way to get the child thread to access the value in the parent thread? The answer is yes, so use the InheritableThreadLocal analyzed by our above principle.
Modify the code (1) of the above example to:
//(1) Create thread variable public static ThreadLocal<String> threadLocal = new InheritableThreadLocal<String>();
The operation results are as follows:
It can be seen that the thread variable value can now be obtained normally from the child thread. So under what circumstances do children need to obtain threadlocal variables from the parent thread?
There are quite a lot of situations, such as the threadlocal variable that stores user login information. It is very likely that the subthreads also need to use user login information. For example, some middleware needs to use a unified tracking ID to record the entire calling link.
Use of ThreadLocal in Spring Request Scope Scope Bean
We know that when configuring a bean in XML in Spring, you can specify the scope attribute to configure the scope of the bean to be singleton, prototype, request, session, etc. The implementation principle of the scope of the bean is implemented using ThreadLocal. If you want a bean in your Spring container to have some scope of the Web,
In addition to the corresponding scope attributes required to configure the Bean level, it must also be configured in web.xml as follows:
<listener> <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class></listener>
Here we mainly look at two methods of RequestContextListener:
public void requestInitialized(ServletRequestEvent requestEvent)
and
public void requestDestroyed(ServletRequestEvent requestEvent)
When a web request comes, the requestInitialized method will be executed:
public void requestInitialized(ServletRequestEvent requestEvent) { .....Omit HttpServletRequest request = (HttpServletRequest) requestEvent.getServletRequest(); ServletRequestAttributes attributes = new ServletRequestAttributes(request); request.setAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE, attributes); LocaleContextHolder.setLocale(request.getLocale()); //Set the attribute to threadlocal variable RequestContextHolder.setRequestAttributes(attributes); } public static void setRequestAttributes(RequestAttributes attributes) { setRequestAttributes(attributes, false); } public static void setRequestAttributes(RequestAttributes attributes, boolean inheritable) { if (attributes == null) { resetRequestAttributes(); } else { //Default inheritable=false if (inheritable) { inheritableRequestAttributesHolder.set(attributes); requestAttributesHolder.remove(); } else { requestAttributesHolder.set(attributes); inheritableRequestAttributesHolder.remove(); } } }You can see the above source code. Since the default inheritable is FALSE, our attribute values are placed in requestAttributesHoder, and its definition is:
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =new NamedThreadLocal<RequestAttributes>("Request attributes"); private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =new NamedInheritableThreadLocal<RequestAttributes>("Request context");Among them, NamedThreadLocal<T> extends ThreadLocal<T>, so it is not inheritable.
Among them, NamedThreadLocal<T> extends ThreadLocal<T>, so it is not inheritable.
NameInheritableThreadLocal<T> extends InheritableThreadLocal<T>, so it has inheritance, so the attribute value placed in the RequestContextHolder by default cannot be obtained in the child thread.
When the request ends, the requestDestroyed method is called, and the source code is as follows:
public void requestDestroyed(ServletRequestEvent requestEvent) { ServletRequestAttributes attributes = (ServletRequestAttributes) requestEvent.getServletRequest().getAttribute(REQUEST_ATTRIBUTES_ATTRIBUTE); ServletRequestAttributes threadAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (threadAttributes != null) { // We are very likely to clear the thread of the current thread in the initial request thread if (attributes == null) { attributes = threadAttributes; } // When the request ends, clear the thread variable of the current thread. LocaleContextHolder.resetLocaleContext(); RequestContextHolder.resetRequestAttributes(); } if (attributes != null) { attributes.requestCompleted(); } }Next, let’s take a look at the logic of web request calling from the timing chart:
That is to say, every time a web request is initiated before processing the context (specific application) in Tomcat, the RequestContextHolder property will be set after the host matches, so that the requestAttributesHolder is not empty and will be cleared at the end of the request.
Therefore, by default, the attribute child threads placed in the RequestContextHolder cannot be accessed, and the beans in the request scope of Spring are implemented using threadlocal.
Next, an example simulation request is performed, the code is as follows:
The web.xml configuration is as follows:
Because it is a request scope, it must be a Web project and the RequestContextListener needs to be configured to web.xml.
<listener> <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class></listener>
Then inject a request scope bean into the IOC container. The code is as follows:
<bean id="requestBean" scope="request"> <property name="name" value="hjc" /> <aop:scoped-proxy /> </bean>
The test code is as follows:
@WebResource("/testService")public class TestRpc { @Autowired private RequestBean requestInfo; @ResourceMapping("test") public ActionResult test(ErrorContext context) { ActionResult result = new ActionResult(); pvgInfo.setName("hjc"); String name = requestInfo.getName(); result.setValue(name); return result; }}As mentioned above, first configure the RequestContextListener into web.xml, then inject the RequestBean instance into the IOC container with the Request scope. Finally, the RequestBean instance is injected into TestRpc. Method test first calls the requestInfo method setName to set the name attribute, then get the name attribute and return.
Here, if the requestInfo object is singleton, after multiple threads call the test method at the same time, each thread is a set-get operation. This operation is not atomic and will cause thread safety problems. The scope declared here is the request level, and each thread has a local variable with requestInfo.
The timing chart of the above example method request is as follows:
We need to focus on what happens when calling test:
In fact, the requestInfo created earlier is after being proxyed by CGliB (if you are interested, you can study ScopedProxyFactoryBean and other types), so when you call setName or getName, you will be intercepted by DynamicAdvisedInterceptor. The interceptor will eventually call the get method of RequestScope to get the local variables held by the current thread.
The key is here. We need to look at the source code of the RequestScope get method as follows:
public Object get(String name, ObjectFactory objectFactory) { RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();//(1) Object scopedObject = attributes.getAttribute(name, getScope()); if (scopedObject == null) { scopedObject = objectFactory.getObject();//(2) attributes.setAttribute(name, scopedObject, getScope());//(3) } return scopedObject; }It can be seen that when a request is initiated, the requestAttributesHolder will be set by calling RequestContextListener.requestInitialized in RequestContextListener.setRequestAttributess.
Then after the request is routed to the Test method of TestRpc, the first time the setName method is called in the test method, the RequestScope.get() method will eventually be called. The code in the get method (1) gets the value of the attribute set saved by the thread-local variable requestAttributesHolder set through RequestContextListener.requestInitialized.
Then check whether there is an attribute named requestInfo in the attribute set. Since it is the first call, it does not exist, so the code will be executed (2) and let Spring create a RequestInfo object, and then set it to the attribute set attributes, that is, it will be saved in the local memory of the current request thread. Then return the created object and call the setName of the created object.
Finally, the getName method is called in the test method, and the RequestScope.get() method will be called. The code in the get method (1) gets the thread local variable RequestAttributes set through RequestContextListener.requestInitialized, and then see if there is an attribute named requestInfo in the attribute set.
Since the bean named requestInfo has been set to the ThreadLocal variable when it is called for setName for the first time, and the same thread is called for setName and getName, the RequestInfo object created when calling setName is directly returned here, and then its getName method is called.
So far, we have understood the implementation principle of ThreadLocal and pointed out that ThreadLocal does not support inheritance; then we immediately explained how InheritableThreadLocal compensates for the feature that ThreadLocal does not support inheritance; finally, we briefly introduced how to use ThreadLocal in the Spring framework to implement the bean of Reqeust Scope.
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support to Wulin.com.