preface
We are all familiar with ThreadLocal in Java, and it is often found in various articles and practical applications. This article introduces the basic use and implementation of threadLocal. Why are ThreadLocal keys weak references? Why are ThreadLocal keys leaking
What is it? What does it do
In a multi-threaded environment, if each thread wants to maintain variables that belong to its own thread, it usually has to encapsulate them in methods, because variables in the method stack cannot be accessed by other threads
ThreadLocal provides the ability to provide a variable that each thread operates on locally, without affecting its value in other threads
This variable can no longer be a local variable defined in a method, but a property of an instance, or a static property of a class (recommended), which makes it easy to manipulate thread-only variables between different methods
By the way, go doesn’t have anything like ThreadLocal, which passes data directly through the Context on a request, so each method takes an extra parameter. This, of course, has become the GO code specification
The basic use
From the user’s perspective, ThreadLocal is essentially a variable, so it should have set, GET, remove, and initinal methods
The ThreadLocal API is as follows:
There are two initialization methods, and one initialization function is set at the bottom. The last static method withInitinal is recommended because it is more concise
The last program demonstrates its basic usage
public class ThreadLocalTest {
// Define the ThreadLocal variable with an initial value of 1
static ThreadLocal<Integer> tl = ThreadLocal.withInitial(()->1);
public static void main(String[] args) throws InterruptedException {
// Start two threads to increment the TL separately
new Thread(()->{
Integer v1 = tl.get();
v1++;
tl.set(v1);
System.out.println(Thread.currentThread().getName() + "-" + tl.get());
}, "t1" ).start();
new Thread(()->{
Integer v1 = tl.get();
v1++;
tl.set(v1);
System.out.println(Thread.currentThread().getName() + "-" + tl.get());
}, "t2" ).start();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "-"+ tl.get()); }}Copy the code
The program uses ThreadLocal on t1, T2, and main threads, respectively, where t1 and T2 are aligned to increment.
t1---2
t2---2
main---1
Copy the code
You can see that each thread’s operations on the ThreadlLocal variable affect only its in-thread replicas. Matches the definition of ThreadlLocal
Since it can only be accessed by each thread, there is no problem of multithreading sharing, so v1++, a non-atomic operation, does not need to be locked to synchronize
Source code analysis
To find out how ThreadLocal works, read the source code
Firstly, analyze the structure of the class:
What is the relationship between threads and ThreadLocal?
Thread in the Thread class has a property: ThreadLocal. ThreadLocalMap
ThreadLocalMap is a static inner class of ThreadLocal
Let’s look at the get and set methods of ThreadLocal
Get source code as follows:
public T get(a) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if(e ! =null) {
T result = (T)e.value;
returnresult; }}return setInitialValue();
}
Copy the code
Get returns the value of the current thread-local variable
- Gets the ThreadLocalMap of the current thread, directly from the threadLocals variable of the current thread
- If the map is not empty, get the value from the map with key this, which is the ThreadLocal itself
- If the map is empty, initialize the map
If set has not been used before, the map must be empty and the setInitialValue method will be used.
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
Steps as follows:
- Call the initialValue method to generate the initialValue
- Call the createMap method to create a ThreadLocalMap and place the initial values into the map
Set method is similar, the source code is as follows, do not repeat
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
When the map is empty at first, it goes to the createMap method:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue); } ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) { table =new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
/ / the key
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
Copy the code
You can see that the map consists of entries, each of which consists of ThreadLocal itself and a value
The memory structure of ThreadLocal is now clear:
All data is stored locally on the thread
Why does Entry inherit WeakReference
Entry is also a static inner class of ThreadLocal:
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
It inherits WeakReference and calls super(k) in the constructor, so that a WeakReference points to the ThrealLocal variable passed in
Then the question comes, what is WeakReference and why should Entry inherit it?
Let’s review javA4 types of references:
- Strong references: This is the most common object reference method. When an object is strongly referenced, it will not be collected by the garbage collection mechanism, even if OOM appears
- SoftReference: a SoftReference is a reference that is weaker than a strong reference. If an object is referenced only by a SoftReference, the object will not be reclaimed if the system memory is sufficient. Otherwise, the object will be reclaimed. Usually used in cache scenarios, avoid OOM due to large cache
- WeakReference: unlike if references, if an object is only referenced by a soft reference, it will be reclaimed as soon as GC occurs
- PhantomReference: this type of reference does not determine the life cycle of the object. It is used together with reference queues to receive system notification when the object is reclaimed. It is generally not used in daily development
Consider the following scenario:
public void func1(){
ThreadLocal<Object> tl = ThreadLocal.withInitial(()->1);
Object o = new Object();
tl.set(o);
tl.get();
}
Copy the code
If the Entry to key is a strong reference, when func1 is executed, the stack frame will be recycled and no strong application will point to the TL. However, if the thread is not terminated, then the key of an Entry of the thread’s ThreadLocalMap will still point to the strong reference of the TL. Although the TL is no longer needed, the object will not be collected by gc due to strong references, causing a memory leak
The strong reference chain is Thread -> ThreadLocalMap -> Entry -> key, as follows:
Therefore, Java officially makes Entry inherit WeakReference, so that the key of the Entry is pointed to by WeakReference. Func1’s key has only a weak reference to it, so it will be recycled whenever garbage collection is run, resulting in null entries in ThreadLocalMap
After that, the set, GET, and remove calls of ThreadLocal will attempt to delete entries with null keys to release the memory occupied by the value, avoiding memory leaks
Of course, the best practice is to define ThreadLocal as a static or class property and then call remove to clean up the data after use, so that you can avoid memory leaks without relying on weak references
Thread pool scenario
In the thread pool scenario, since threads are not destroyed after processing a single piece of business logic but are put back into the thread pool, previously stored ThreadLocal variables are also retained
ThreadLocal stores data related to one request, such as userID, which may cause problems if the data is retrieved by the next request
Therefore, after using ThreadLocal, you need to call the remove method to remove the data
conclusion
- ThreadLocal is suitable for storing data that is isolated between threads but shared between methods
- Because the data is stored in thread-local variables and can only be accessed by the current thread itself, there is no thread-safety issue
- The Entry of a ThreadLocalMap references the key (ThreadLocal) as a weak reference, avoiding memory leaks
- The best practice is to define ThreadLocal as a static or class property and then call remove to clean up the data after use, thus avoiding memory leaks without relying on weak references