A ThreadLocal is called a local thread variable. A ThreadLocal is filled with variables of the current thread that are closed and isolated from other threads. A ThreadLocal creates a copy of the variable in each thread, so that each thread can access its own internal copy of the variable.
1. Application scenarios
There are two typical usage scenarios for ThreadLocal in general business development.
Scenario 1, where ThreadLocal is used to hold objects that are exclusive to each thread, creates a copy for each thread so that each thread can modify its own copy without affecting other threads’ copies, ensuring thread-safety.
Scenario 2, ThreadLocal is used as a scenario where information needs to be kept independently within each thread so that it can be more easily retrieved by other methods. Each thread may get different information. After the previous method holds the information, subsequent methods can get the information directly through ThreadLocal, avoiding passing arguments, similar to the concept of global variables.
Scenario 1
This scenario is often used to hold thread-unsafe utility classes, and a typical class that needs to be used is SimpleDateFormat.
Each Thread has a copy of its own instance that can only be accessed and used by the current Thread. This copy is equivalent to a local variable within each Thread, which is what ThreadLocal is named for.
Because each thread has a unique copy, not a common one, there is no problem with sharing between multiple threads.
Scenario 2
Each thread needs to hold information similar to global variables (such as user information retrieved in the interceptor) that can be used directly by different methods, avoiding the hassle of passing parameters but not sharing them with multiple threads (because different threads get different user information).
The SecurityContext class is similar to Spring Security.
2. Pay attention
Do you resolve sharing issues?
Interview question: Is ThreadLocal a solution to the problem of multithreaded access to shared resources?
No, ThreadLocal is not designed to solve shared resource problems. While ThreadLocal can indeed be used to address thread-safety issues in multi-threaded situations, its resources are not shared, but are exclusive to each thread.
ThreadLocal solves thread-safety problems in a different way than using “locks” by making resources exclusive to each thread, which cleverly avoids synchronization.
If we add static to a ThreadLocal resource and make it a shared resource, there are thread safety issues even with ThreadLocal.
Relationship between ThreadLocal and synchronized
Interview question: What is the relationship between ThreadLocal and synchronized?
When ThreadLocal is used to address thread-safety issues, that is, giving each thread a unique copy of an object, both ThreadLocal and synchronized can be understood as thread-safety measures.
ThreadLocal
By giving each thread exclusive access to its own copy, it avoids competing for resources.synchronized
It is used to allocate a critical resource that can be accessed by at most one thread at a time.
Synchronized is less efficient than ThreadLocal, but it also consumes less memory. In this scenario, ThreadLocal and synchronized have different effects, but both can achieve thread-safety.
There are also different usage scenarios for ThreadLocal. For example, when ThreadLocal is used to make it easier for multiple classes to access scenarios where we want to store this information independently for each thread (such as a user object for each thread), the focus of ThreadLocal is to avoid passing parameters. So ThreadLocal and synchronized are two different dimensional tools at this point.
3. Source code analysis
The relationship between Thread, ThreadLocal, and ThreadLocalMap
Each Thread object holds a member variable of type ThreadLocalMap.
ThreadLocalMap itself is like a Map, with key-value pairs in the form of key values.
The key is a reference to the ThreadLocal, and the value is what we want the ThreadLocal to store.
A Thread has only one ThreadLocalMap, but a ThreadLocalMap can have many ThreadLocal’s, each corresponding to a value. Since a Thread can call multiple ThreadLocal’s, a Map data structure called ThreadLocalMap is used to store ThreadLocal’s and values.
The get method
public T get(a) {
// Get the current thread
Thread t = Thread.currentThread();
A ThreadLocalMap object is available in each thread
ThreadLocalMap map = getMap(t);
if(map ! =null) {
// Get the Entry in ThreadLocalMap, which holds the keys and values
ThreadLocalMap.Entry e = map.getEntry(this);
if(e ! =null) {
@SuppressWarnings("unchecked")
/ / return a value
T result = (T)e.value;
returnresult; }}ThreadLocalMap is created if it has not been created before
return setInitialValue();
}
// Get the ThreadLocalMap object of the current thread
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// Thread attributes in the source code
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocalMap is initialized before it is initialized
private T setInitialValue(a) {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null)
map.set(this, value);
else
createMap(t, value);
return value;
}
Copy the code
Set method
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
// If the map is not initialized, initialize it and put the value in
if(map ! =null)
map.set(this, value);
else
createMap(t, value);
}
Copy the code
ThreadLocalMap class
static class ThreadLocalMap {
/** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. * /
// Inherits virtual references to prevent memory leaks
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
ThreadLocalMap resolves hash collisions in a different way, using linear probing. If there is a conflict, it does not chain down as a linked list, but continues to find the next empty cell. This is the point where ThreadLocalMap and HashMap handle conflicts differently.
4. Memory leakage
A memory leak is a memory that cannot be reclaimed when an object is no longer useful. This is called a memory leak.
Because normally, if an object is no longer useful, then our garbage collector, THE GC, should clean up the memory. In this way, the memory can be subsequently reallocated to other places to use; Otherwise, if the object is not used but cannot be collected, such garbage objects will accumulate more and more, resulting in less and less available memory, and eventually an OOM error will occur.
The key of leakage
Each Thread has a ThreadLocal ThreadLocalMap such type variable, the variable name is threadLocals.
After a thread accesses a ThreadLocal, it maintains the mapping of that ThreadLocal variable to a specific instance in an Entry in its ThreadLocalMap.
We might perform a ThreadLocal instance = NULL operation in our business code and want to clean up the ThreadLocal instance, but suppose we have a strong reference to the ThreadLocal instance in the Entry of the ThreadLocalMap, So, even though the ThreadLocal instance is null in the business code, the chain of references still exists in the Thread class.
The GC does a reachability analysis at garbage collection time and finds that the ThreadLocal object is still reachability, so no garbage collection is performed on the ThreadLocal object, causing a memory leak.
The JDK developers took this into account, so the Entry in ThreadLocalMap inherits the WeakReference WeakReference, as shown below:
static class Entry extends WeakReference<ThreadLocal<? >>{
/** The value associated with this ThreadLocal. */Object value; Entry(ThreadLocal<? > k, Object v) {// The super() constructor is weakly referenced
super(k);
// This line is a strong referencevalue = v; }}Copy the code
The nature of weak references is that if the object is only associated with weak references and there is no strong reference association, then the object can be reclaimed, so weak references do not prevent GC. Therefore, this weak-reference mechanism avoids the memory leak problem of ThreadLocal.
The value of the leakage
Normally, when the thread terminates, the value corresponding to the key can be garbage collected because no strong references exist.
However, sometimes threads have long life cycles, and if threads don’t terminate, ThreadLocal and its corresponding value may no longer be useful. In this case, we should ensure that they can be recycled normally.
Let’s look at specific reference links (solid lines for strong references, dotted lines for weak references) :
Thread Ref → Current Thread → ThreadLocalMap → Entry → Value → Value instance that may leak.
If a thread executes a time-consuming task and does not stop, then the Value is reachable when the garbage collection does reachability analysis, so it will not be collected. But at the same time, we may have finished processing the business logic and no longer need the Value, at which point a memory leak occurs.
The JDK also takes this issue into account. When ThreadLocal’s set, remove, and rehash methods are executed, it scans for an Entry with a null key. It sets the corresponding value to null, and the value object can be collected normally.
However, if ThreadLocal is no longer used, the set, remove, and rehash methods will not actually be called, and if the thread is still alive, the chain of calls will always exist, causing the value to leak.
How to avoid memory leaks
Call the remove method of ThreadLocal. Calling this method removes the corresponding value object, avoiding a memory leak.
Source:
public void remove(a) {
ThreadLocalMap m = getMap(Thread.currentThread());
if(m ! =null)
m.remove(this);
}
// This method is actually called
private void remove(ThreadLocal
key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for(Entry e = tab[i]; e ! =null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return; }}}Copy the code
Therefore, after using ThreadLocal, we should manually call its remove method to prevent memory leaks.