We are all in the gutter, but some of us are looking at the stars


We are all in the gutter, but some of us are looking at the stars

Today we’re going to talk about ThreadLocal and how it works. I’m going to talk about ThreadLocal and how it works


ThreadLocal is an internal storage class that can store data in a specified thread. Once the data is stored, only the specified thread can get the stored data


In the following example, we start two threads, set the value of the local variable inside each thread, and then call print to print the value of the current local variable. Calling the remove method of local variables after printing removes variables from local memory, as shown below

public class ThreadLocalMain {    
    
    static ThreadLocal<String> threadLocal = new ThreadLocal<>();    
    
    static void print(String str){        
        System.out.println(str + ":"+threadLocal.get());        
        threadLocal.remove();    
    }    

    public static void main(String[] args){        
        
        Thread thread1 = new Thread(new Runnable() {            
        
        @Override            
        public void runThreadlocal.set () {// set threadlocal.set ();"thread1");                
            print("thread1");                
            System.out.println("after remove+"+threadLocal.get()); }}); Thread thread2 = new Thread(newRunnable() {            
        @Override            
        public void runThreadlocal.set () {// set threadlocal.set ();"thread2");                
            print("thread2");                
            System.out.println("after remove+"+threadLocal.get()); }}); thread1.start(); thread2.start(); }Copy the code

Thread1 :thread1 Thread2 :thread2 after remove+null After remove+nullCopy the code

ThreadLocal implementation principle:

Let’s start with a few threadLocal methods:

public void setT = thread.currentThread (); ThreadLocalMap map = getMap(t); // If there is a mapsetIf no, create a map andset
      if(map ! = null) map.set(this, value);else
          createMap(t, value);
  }
 
public T getThread 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;            
        returnresult; }}return setInitialValue(); } //getMap getMap(Thread t) {// Get the Thread's own variable threadLocals and bind it to the member variable threadLocals of the current calling Threadreturnt.threadLocals; } //createMap void createMap(Thread t, t firstValue) { ThreadLocals. Htreadlocals = new ThreadLocalMap(this, firstValue); }Copy the code

As you can see from the code above
Each thread holds a ThreadLocalMap object. Each new Thread instantiates a ThreadLocalMap and assigns it to the member variable threadLocals. If threadLocals already exists, the existing object will be used.


If the getMap method does not return null, the value is directly set to threadLocals (key is the current thread reference and value is a local variable). If the getMap method returns null, it is the first call to the set method (as mentioned earlier, the default value of threadLocals is null, and the map is created only when the set method is called). In this case, the createMap method is called to create threadLocals

CreateMap void createMap(Thread t, t firstValue) { ThreadLocals. Htreadlocals = new ThreadLocalMap(this, firstValue); }Copy the code

The createMap method not only creates threadLocals, but also adds local variable values to threadLocals

Get method

In the implementation of the get method, the current caller thread is first obtained. If threadLocals of the current thread is not null, the value of the local variable bound by the current thread is returned directly. Otherwise, the setInitialValue method is used to initialize the threadLocals variable. The setInitialValue method, similar to the set method, determines whether the threadLocals variable of the current thread is null. If it is, the local variable is added. Otherwise, the threadLocals variable is created. The same added value is null.

public T getThread t = thread.currentThread (); ThreadLocalMap map = getMap(t); // If threadlocals is not empty, the value of the local variable will be found in the mapif(map ! = null) { ThreadLocalMap.Entry e = map.getEntry(this);if(e ! = null) { @SuppressWarnings("unchecked")            
        T result = (T)e.value;            
        returnresult; }} // At this point, threadLocals is null. Call the threadLocals variable that initializes the current threadreturn setInitialValue();
}Copy the code

The implementation of remove method

public void removeThreadLocals ThreadLocalMap m = getMap(thread.currentThread ()); // If map is not null, the local variable of the specified ThreadLocal instance in the current thread is removedif(m ! = null) m.remove(this); }Copy the code
When using ThreadLocal, you need to manually remove the ThreadlocalMap from ThreadLocal to avoid memory leaks

Why remove after each use

When a thread calls ThreadLocal’s set method to set a variable, the current thread’s ThreadLocalMap stores a record whose key is the reference to ThreadLocal and whose value is the set value.
If the current thread persists without calling Threadlocal’s remove method, and there are references to Threadlocal elsewhere, The ThreadLocalMap variable of the current thread contains references to ThreadLocal variables and references to value objects that will not be released, causing a memory leak. But consider that if the ThreadLocal variable has no other strong dependencies and the current thread still exists, since the key in the thread’s ThreadLocalMap is weak reference,
Weak references to ThreadLocal variables in the current thread’s ThreadLocalMap will be reclaimed during GC, but the corresponding values will still leak memoryIn ThreadLocalMap, there will be an entry whose key is null but value is not null. So, remember remove to avoid memory leakage

Why weak references



On the surface, a memory leak occurs because the Key uses a weak reference type. However, the value is not cleared after the key of the entire Entry is null.


Let’s discuss the following two cases:
  • Key uses strong references: The object referenced by ThreadLocal is reclaimed, but ThreadLocalMap also holds a strong reference to ThreadLocal. Without manual deletion, ThreadLocal will not be reclaimed, resulting in Entry memory leaks.
  • Key uses weak references: The object referenced by ThreadLocal is reclaimed, and since ThreadLocalMap holds a weak reference to ThreadLocal, ThreadLocal is reclaimed even without manual deletion. The value is cleared the next time ThreadLocalMap calls set,get, and remove.
Since ThreadLocalMap has the same lifetime as Thread, if the corresponding key is not manually removed, memory leaks will occur, but using weak references provides an additional layer of protection: Weak reference ThreadLocal does not leak memory and the corresponding value is cleared the next time ThreadLocalMap calls set, GET, or remove.
Thus, the root cause of a ThreadLocal memory leak is that since ThreadLocalMap has the same lifetime as Thread, a memory leak would result if the value of the corresponding key was not manually removed, not because of weak references.

conclusion

In the case of thread pools, not cleaning up ThreadLocal in a timely manner is not only a memory leak problem, but more importantly, it can lead to problems with business logic. So using ThreadLocal is like unlocking a lock and cleaning it up when you’re done.