preface
Even if you don’t use ThreadLocal directly, it also appears indirectly in frameworks you use, such as Spring transaction management, Hibernate Session management, logBack (and Log4j) MDC functionality implementation, etc. In project development, for example, some paging functions are implemented with the help of ThreadLocal.
Because of ThreadLocal’s ubiquity, it’s not uncommon to be asked in interviews about its implementation, core API usage, and memory leaks.
And based on these problems can be extended to thread safety, JVM memory management and analysis, Hash algorithm and other knowledge points. You can see how important ThreadLocal is to developers. If you don’t have the full picture, then this article is worth studying in depth.
What is a ThreadLocal
ThreadLocal is Therad’s local variable maintenance class, which is stored as a special variable in Java. When using ThreadLocal to maintain variables, ThreadLocal provides a separate copy of the variable for each thread that uses the variable, so each thread can independently change its own copy without affecting the corresponding copy of other threads.
Because each Thread has its own instance copy that can only be used by the current Thread, there is no problem with sharing between threads.
In general, ThreadLocal is suitable for scenarios where each thread needs its own independent instance and that instance needs to be used in multiple methods, that is, variables are isolated between threads and shared between methods or classes.
For example, if there is a variable count, operating on count++ when multiple threads are running concurrently can cause thread-safety problems. With ThreadLocal, however, you can create a count copy for each thread that belongs only to the current thread and operate on its own copy without affecting other threads.
On the other hand, a ThreadLocal is a data structure, somewhat like a HashMap, that can hold “key:value” key-value pairs, but a ThreadLocal can hold only one key-value pair, and the data of each thread is independent of each other.
@Test public void test1(){ ThreadLocal<String> localName = new ThreadLocal<>(); // Only one set method is provided; Localname.set (" program new horizon "); // Only one get method is provided. String name = localname.get (); System.out.println(name); }Copy the code
In the code above, thread A initializes A ThreadLocal object and calls the set method, holding A value. This value is available only when thread A calls the get method. Thread B can’t get it if it calls get. As for how to achieve this function, we will explain in the following source code analysis, here know its function.
Example ThreadLocal
Given the usage scenarios and basic implementation theory, let’s take a look at how ThreadLocal can be used with a simple example.
Public class ThreadLocalMain {/** * ThreadLocal, each thread has a copy, Public static final ThreadLocal<String> HOLDER = new ThreadLocal<>(); public static void main(String[] args) throws Exception { new ThreadLocalMain().execute(); } public void execute() throws Exception {// Main thread set value holder.set (" program new horizon "); Thread.out.println (thread.currentThread ().getName() + "ThreadLocal:" + holder.get ()); New Thread(() -> {system.out.println (thread.currentThread ().getName()) + "+ holder.get ()); // Set the value holder.set (" Program horizon ") in the current thread; System.out.println(" after reset, "+ Thread.currentThread().getName() +" ThreadLocal: "+ holder.get ()); System.out.println(thread.currentThread ().getName() + "Thread terminated "); }).start(); Thread.sleep(1000L); Thread.out.println (thread.currentThread ().getName() + "ThreadLocal:" + holder.get ()); }}Copy the code
The example defines a static final ThreadLocal variable HOLDER, simulating two threads in the main method to manipulate the values stored in the HOLDER. The HOLDER is set to a value, and the obtained value is printed. A new thread is created to modify the value in the HOLDER, and the corresponding value is fetched from both the new thread and the main thread.
Execute the program and print the following results:
ThreadLocal = null; ThreadLocal = null The value in the main Thread ThreadLocal: the program new horizonCopy the code
Comparing the program with the output, you can see that the main Thread and Thread-0 each have their own variable store. The main Thread is not changed because Thread0 called HOLDER’s set method.
This is possible because in ThreadLocal, each Thread has a copy of its own variable, and multiple threads do not interfere with each other. So how, you might wonder, does ThreadLocal accomplish this?
Analysis of ThreadLocal principle
Before we look at how ThreadLocal works, let’s take a look at some of the theories and data structures.
Basic process and source code implementation
Within a single Thread can save multiple ThreadLocal object, the location of the store is located in the Thread of the ThreadLocal. ThreadLocalMap variables, in the Thread has the following variables:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Copy the code
ThreadLocalMap is a static inner class maintained by ThreadLocal, as noted in the code note that this variable is maintained by ThreadLocal.
When we use the get() and set() methods of ThreadLocalMap, we actually call the corresponding get() and set() methods of ThreadLocalMap.
This variable in Thread is usually initialized when ThreadLocal’s get() and set() methods are first called.
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
In the set method above, we get the current thread object first, and then use getMap to get threadLocals from the current thread:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
Copy the code
If the corresponding attribute in Thread is null, create a ThreadLocalMap and assign it to Thread:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Copy the code
If it already exists, set the value using the set method of ThreadLocalMap. Here we can see that the set key is this, which is the current ThreadLocal object, and the value is the value we want to store.
The corresponding get method source is as follows:
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! = null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e ! = null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }Copy the code
As you can see, also from the current thread, get the threadLocals property of the current thread and return the stored value from it. During get, if the threadLocals property of the Thread is not initialized, the createMap method is indirectly called to initialize the Thread.
Let’s summarize the above processes through a flow chart:
In JDK8 and 9, the createMap method is not used to initialize the Thread locals property. It is not clear how the JVM performs the initialization assignment. When testing JDK13 and JDK14, the createMap method was obviously used.
Data structure of ThreadLoalMap
ThreadLoalMap is a static internal class in ThreadLocal that is similar to a HashMap data structure but does not implement a Map interface.
ThreadLoalMap initializes an Entry array of size 16. The Entry object is used to hold each key-value pair. We already know from the set method above that the key is always a ThreadLocal object.
Take a look at the relevant source code:
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<? >> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<? > k, Object v) { super(k); value = v; } } private static final int INITIAL_CAPACITY = 16; / /... }Copy the code
The class diagram structure of ThreadLoalMap is as follows:
The important thing to note here is that the Entry object in the ThreadLocalMap class inherits from a WeakReference, which means it is a WeakReference. There are memory leaks, which we’ll talk about later.
Since hreadLocalMaps are created lazily, at least one Entry object is created during construction. This can be seen in the constructor:
ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); }Copy the code
The above constructor creates an Entry array with a default length of 16 and uses the hashCode and length bits to determine the index value I. As mentioned above, each Thread has a variable of type ThreadLocalMap.
So far, combined with Thread, we can see the entire data model as follows:
Hash conflict and resolution
Note that in the constructor the Entry location in the table is obtained using the Hashcode algorithm. Each ThreadLocal object has a hash value of threadLocalHashCode, and each time a ThreadLocal object is initialized, the hash value increases by a fixed size 0x61C88647.
When an Entry object is stored into an Entry value in a ThreadLocalMap, it is positioned to position I in the table based on the hash value of the ThreadLocal object. There are three cases:
- If the current location is empty, the Entry is directly stored in the corresponding location.
- If position I already has a value and the key of the Entry object happens to be the key to be set, then reset the value in the Entry.
- If the Entry object of position I is unrelated to the key to be set, look for an empty position;
Hash conflicts occur when calculating hash values. Common solutions include rehash, open address, public overflow area, and chained address.
The above process can be seen here is open address method, if the current position value, will continue to look for the next position, pay attention to the table [len – 1] is the next position table [0], like a circular array, so also called closed hashing. If you can’t find the empty space all the time, an infinite loop will occur, and memory will overflow. Of course, there is a mechanism to expand capacity, won’t find empty position commonly.
ThreadLocal memory leaks
Improper use of ThreadLocal can result in memory leaks, which in turn can lead to memory overruns. Let’s take a look at the causes of memory leaks and related design ideas.
Memory reference link
According to the previous analysis of ThreadLocal, each Thread maintains a ThreadLocalMap, whose key is the ThreadLocal instance itself and value is the Object that services need to store. That is, ThreadLocal does not store values itself; it simply acts as a key for the thread to retrieve values from ThreadLocalMap.
Take a close look at the ThreadLocalMap, which uses a weak reference to ThreadLocal as its Key, and the weakly referenced object is reclaimed during GC. So with ThreadLocal, the chain of references looks like this:
Dashed lines indicate weak references. Let’s start by looking at the classification of references in Java.
References in Java
There are typically the following types of references in Java: strong, weak, soft, and virtual.
- Strong references: In general, new objects are strong references. As long as the reference exists, the garbage collector will never reclaim the referenced object, even when memory is low.
- SoftReference: an object with a SoftReference modifier is called a SoftReference. The object to which a SoftReference points is reclaimed when its memory is about to overflow. An out-of-memory exception is thrown if there is not enough memory left after the collection.
- WeakReference: objects modified with WeakReference are called weak references. Whenever garbage collection occurs, object instances associated only with weak references will be reclaimed regardless of whether the current memory is sufficient.
- Virtual References: Virtual references are the weakest references and are defined in Java using PhantomReference. The only purpose of a virtual reference is to queue up notifications that an object is about to die.
Cause analysis of leakage
Normally, threads are destroyed when they are finished executing, and the ThreadLocalMap instance pointed to by Thread.threadlocals becomes garbage and the Entity stored in it is reclaimed. There is no memory leak in this case.
The memory leak scenario usually exists in the case of thread pools. In this case, the Thread life cycle is long, and the threadLocals reference will always exist. When the ThreadLocal stored in it is reclaimed (the weak reference life cycle is short), the Entity will become an instance with a null key, but the value will not be reclaimed. If the Entity is never get(), set(), or remove(), it is never reclaimed and a memory leak occurs.
Therefore, the remove() method is usually called after ThreadLocal is used to clean up memory.
For example, in a Web request, we can call the recycle method through filters and so on:
Public void doFilter(ServeletRequest Request, ServletResponse){try{// Set ThreadLocal localname.set (" new view "); Chain. DoFilter (request, response)}finally{// Call remove to overflow the localname.remove () variable in threadLocal; }}Copy the code
Why weak references instead of strong references?
The apparent root cause of the memory leak is the use of weak references, but why did the JDK adopt a weak-reference implementation instead of a strong reference?
Let’s start with a comment on the ThreadLocalMap class:
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys. WeakReference is used as a key in hash table entries to help deal with scenarios with large data and long life cycle.Copy the code
This is somewhat different from what we might expect, but weak references are specifically used to solve memory storage problems.
Let’s assume that if the key uses a strong reference, then all other objects that hold ThreadLocal references are reclaimed, but ThreadLocalMap still holds a strong reference to ThreadLocal. This prevents the ThreadLocal from being reclaimed, resulting in an Entry memory leak.
Compare that with weak references. Objects that hold ThreadLocal references are reclaimed, and ThreadLocalMap holds weak references to ThreadLocal that are automatically reclaimed. The corresponding value, however, needs to be cleared the next time the set/get/remove methods are called.
When ThreadLocalMap calls set, get, or remove, the key will be null and the corresponding value will be removed.
So, the root cause of a memory leak is whether or not a manual cleanup operation, not a weak reference.
Application scenario of ThreadLocal
Finally, let’s review the application scenario of ThreadLocal:
- Data isolation between threads, ThreadLocal of each thread does not affect each other;
- Convenient for the same thread to use a certain object, avoid unnecessary parameter passing;
- ThreadLocal is generally used for traceId in full link tracing or context transfer in process engine.
- The Spring transaction manager uses ThreadLocal;
- The implementation of Spring MVC’s RequestContextHolder uses ThreadLocal;
summary
In this article, we analyze the usage scenarios, source code, class structure, and memory structure of ThreadLocal, and finally analyze the root cause of memory leaks. Through the study of this article, you can basically grasp 90% of the core knowledge of ThreadLocal. Did you learn?
The interview series
- Interview questions: Talk about TCP sticky, unpack, and Solutions
- Interview question: Why does overriding equals usually override hashCode?
- Interviewer: How to find the longest string in a string without repetition?
- Don’t Understand Java generics? Just use this article to make sure you have a good interview answer.
- Interview Question: 8 Ways to Invert a string. What can you Think of?
Program new horizon
\
The public account “program new vision”, a platform for simultaneous improvement of soft power and hard technology, provides massive information
\