As is known to all, concurrency problems tend to occur when multiple threads access the same static variable, especially when multiple threads write to a variable. In order to ensure thread safety, ordinary users need to carry out additional synchronization measures when accessing shared variables to ensure thread safety. ThreadLocal is an alternative to locking. It implements a mechanism that makes a copy of a static variable, with each thread accessing only one copy, avoiding direct manipulation of race variables and eliminating concurrency problems. So how does ThreadLocal work? Let’s take the veil off ThreadLocal with a piece of code.
ThreadLocal fundamentals
public class ThreadLocalDemo { public static ThreadLocal<String> threadLocal = new ThreadLocal<String>(); public static void main(String[] args) { ThreadLocalDemo.threadLocal.set("hello world main"); Thread.out.println (" thread.currentThread ().getName() + "threadLocal: " + ThreadLocalDemo.threadLocal.get()); try { Thread thread = new Thread() { @Override public void run() { ThreadLocalDemo.threadLocal.set("new thread"); System. The out. Println (" new Thread "+ Thread. CurrentThread (). The getName () +" threadlocal character value is: "+ ThreadLocalDemo. Threadlocal. The get ()); }}; thread.start(); thread.join(); } catch (Exception e) { System.out.println(e); } system.out.println (thread.currentThread ().getName() + "threadLocal"); " + ThreadLocalDemo.threadLocal.get()); }}Copy the code
The logic of the code is simple: Create a new thread in the main thread and change the threadLocal character value to “new Thread” in the run method of the new thread. The main thread then prints the threadLocal character value again. To ensure that the new thread prints the value of threadLocal before the main thread prints the second time, the join method is used to force the new thread to “block” the main thread until the new thread completes the run method and the main thread continues printing. The result is as follows:
As a result, the new thread does not change the character value of the main thread’s threadLocal, even if the threadLocal type is static. The threadLocal that the new thread modifs is a copy of the threadLocal of the main thread. Let’s source code line by line analysis to get to know, at first we see the first line of code in the main method: first ThreadLocalDemo. ThreadLocal. Set (” hello world main “); Click on the set method and the source code is as follows:
/** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! = null) map.set(this, value); else createMap(t, value); }Copy the code
There is a class called ThreadLocalMap, and an instance of this class is obtained by a method called getMap(t), which stores the address of the threadLocal variable as the key and the character value as value in the map. If the map is empty, createMap(t,value) will be called to create a map.
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Copy the code
ThreadLocals Thread class is a static member variables, it is the type of the Thread of a static inner class ThreadLocalMap, as shown in the following: ThreadLocal. ThreadLocalMap threadLocals = null;
To summarize the above steps, we use a diagram. When the program starts, the code is executed:public static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
At this point, the overall stack memory and heap memory are shown in the following figure:Then, in the main method:ThreadLocalDemo.threadLocal.set("hello world main");
This procedure creates a new Instance of ThreadLocalMap, whose key points to the ThreadLocal object, value is “Hello World main”, and whose key is a weak reference, as shown below:Thread is then created in the main method and called again in the Thread methodThreadLocalDemo.threadLocal.set("new thread");
Therefore, two objects will be created in heap memory, one is Thread object, representing the new Thread; One is an instance of Thread’s ThreadLoaclMap, as shown below:Conclusion: Each time the threadlocal.set (” XXX “) method is called in a new thread, a new instance of ThreadLocalMap is created in the heap memory. This instance holds keys and values in the form of an Entry. Values are different. The keys all point to the same ThreadLocal object.
Why weak references
We know that Java references are divided into four types: strong, soft, weak, and virtual. The definition of weak references is that if an object is referred to by only one weak reference, it must be collected by the garbage collector when the next GC comes. ThreadLocalMap:
static class Entry extends WeakReference<ThreadLocal<? >> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<? > k, Object v) { super(k); value = v; }}Copy the code
We observe that ThreadLocalMap’s key inherits weak references. Why? Light combined with the definition of certainly can not be deeply understood, let us combine the diagram to analyze. Again, assume that the two dotted lines are not weak references, but strong references, as shown in the red line below:At this point, suppose we add a line of code to the main thread or a new thread:
ThreadLocalDemo.threadLocal = null;
That we take the initiative to release the ThreadLocalDemo. ThreadLocal references in the two threads, the results shown in the figure below:We can see that although both threads have voluntarily released the reference to the ThreadLocal object, the reachable path still exists from the main thread reference ->ThreadLocal object. It is well known that mainstream JVMS today use reachable paths rather than reference counting to determine whether an object is recyclable or not. The reachable path algorithm starts with GCROOT, and if there is a strong reference path to an object, the object will never be reclaimed (even in OOM). The thread reference is a local variable of the main thread. According to the GCROOT algorithm, the thread reference can be used as a GCROOT. We explicitly release the threadLocal references (ThreadLocalDemo threadLocal = null) However, due to the existence of a reachable path in GCROOT, the application does not immediately release the ThreadLocal object as we would like it to. It does not release the ThreadLocal object until all of our threads have been released, i.e. the application terminates. This is undoubtedly a memory leak. To solve this problem, we replace the red line in the figure with a weak reference, as shown below
Ever used ThreadLocal?
Yes, in the early days when distributed locking was not implemented by Redission and distributed locking was still implemented by Jedis, we put RedisLockService into the Spring container. This service encapsulates the method of locking and unlocking. If a service is a singleton, it needs to be unlocked by a ThreadLocal thread. If a service is a singleton, it needs to be unlocked by a ThreadLocal thread. If a service is a singleton, it needs to be unlocked by a ThreadLocal thread. During unlocking, check whether the UUID obtained from redis is the same as that of the current thread only when they are the same.