First, what is a ThreadLocal?

Look at the definition in the source code:

/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
 */
Copy the code

ThreadLoacl provides thread-local variables. These variables, unlike normal variables, have a separate copy in each thread. Data in a threadLocal can be manipulated using sets and get, but only changes data in the thread itself. Does not affect other threads;

This may be confusing, but take a look at the code, the explanation without the code is soulless…

  public static void main(String[] args) {

        local=new ThreadLocal<>(); // Change the variable local.set() in the main thread."I am the main");
        System.out.println("main:"+local.get()); New Thread();"ThreadA"){
            @Override
            public void run() {
                super.run();
                local.set("I am ThreadA");
                System.out.println("ThreadA:"+local.get()); } }.start(); // New Thread() is not initialized in the B Thread."ThreadB"){
            @Override
            public void run() {
                super.run();
                System.out.println("ThreadB:"+local.get());
            }
        }.start();


    }
Copy the code

Take a look at the print:

System.out: main: I am main system. out: ThreadA: I am ThreadA system. out: ThreadB: NULLCopy the code

Main, threadA, and threadB each have a separate copy of each other. We can use the set and get methods to manipulate variables in this thread without affecting variables in other threads.

Why is that?

Let’s analyze it from the source code:

  1. ThreadLocalMap is a custom hashMap for maintaining a Thread’s local value, which is normally only used in the Thread class.
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; } } 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);
        }
        
 
        private void set(ThreadLocal<? > key, Object value) { 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)]) { ThreadLocal<? > k = e.get();if (k == key) {
                    e.value = value;
                    return;
                }
                if(k ==null) {// If k==null, remove the reference to value to prevent disclosure of replaceStaleEntry(key, value, I);return;
                }
            }
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if(! cleanSomeSlots(i, sz) && sz >= threshold)rehash(a); }Copy the code

The source code of ThreadLocalMap is not complicated, I just look at some important methods here, omits some useless code, from the above code can see that ThreadLocalMap uses the Entry class to store, with ThreadLocal as the key; In the set method or the constructor to store the assignment. Each call in the set method checks the data and clears the value whose key is null

Note: A ThreadLocalMap uses a weak reference to a ThreadLocal as its key. If a ThreadLocal does not have a strong external reference to it, then the ThreadLocal must be collected during system GC. However, ThreadLocalMap’s lifetime is the same as that of Thread. In this case, there is no way to access the value of an Entry with a null key in a ThreadLocalMap. These entries with null keys will always have a strong reference chain: Thread -> ThreaLocalMap -> Entry -> value will never be reclaimed, resulting in a memory leak. Each call to the set method is checked and the value with null key is cleared, but the value of the last set will always be there and there is a possibility of leakage when the set is recycled. To avoid this leakage, you need to manually call the Remove () method of ThreadLocalMap

  1. Let’s move on to the threadLoacal method

There’s nothing going on in the constructor, so let’s start directly with the set method:

public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t);if(map ! = null) // Store the map.set(this, value) with ThreadLocal as key;else// Initialize and store value createMap(t, value); // Initialize and store value createMap(t, value); } ThreadLocalMap getMap(Thread t) {return t.threadLocals;
    } 
    
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
Copy the code

The set method is simple. It gets the ThreadLocalMap maintained in the current thread, and if not initialized, it calls createMap() to initialize it and store it with the current threadLocal as the key

  1. The get method
  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;
                returnresult; }}return setInitialValue();
    }
    
    
    private T setInitialValue() {
        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

As you can see, the get method is very simple, simply fetching data from the ThreadLocalMap for the current thread and initializing the ThreadLocalMap when not initialized. The setInitialValue method initializes the map and sets the default value. As you can see, if the ThreadLocalMap obtained by getMap is empty, or if the current thread has not yet assigned a value, InitialValue () defaults to null. We can change the default value by overriding this method;