Article catalogue (standing on the shoulders of predecessors summed up this blog, due to the limited level, if there is a mistake to welcome direct spray) :

  1. What problem does ThreadLocal solve
  2. ThreadLocal usage and its usage scenarios
  3. Implementation principle of ThreadLocal
  4. ThreadLocal does not support inheritance
  5. The function of the InheritableThreadLocal class

1 What problem does ThreadLocal solve

There are countless blog posts about the principle of ThreadLocal. Most of the articles found on Baidu have some misconceptions about the scenarios and problems of ThreadLocal. Most of the blog posts from Google search will point out and correct the misconceptions, and the most common misconceptions online are as follows:

  1. ThreadLocal provides a new way to solve the concurrency problem of multithreaded programs
  2. The purpose of ThreadLocal is to solve the problem of sharing resources when multiple threads access them

So why is it pointed out that these two things are wrong? Let’s look directly at the source code description of ThreadLocal:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. 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).

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

ThreadLocal is not actually provided by the J.U.C package; it comes from the java.lang package. As the source code points out, ThreadLocal provides thread-local variables, and each thread that uses the variable has its own independently initialized copy of the variable. It will not conflict with other threads and realize data isolation between threads. As long as the thread is alive and ThreadLocal instances are accessible, each thread keeps an implicit reference to a copy of its thread-local variables. After a thread disappears, all copies of the ThreadLocal instance are garbage collected (unless there are other references to those copies), so ThreadLocal instances are usually set to a private static field.

As we can see, there are no data sharing issues with ThreadLocal, and therefore no synchronization issues, so comparing ThreadLocal to multi-threaded concurrency issues seems a bit out of place, and it’s no surprise that many blog posts have criticized it.


2 ThreadLocal usage and usage scenarios

2.1 ThreadLocal usage

Here’s a simple example of the use of ThreadLocal from the beauty of concurrent programming. The following code opens two threads, each with local variables set internally, and then calls localVariable to print the value of the current localVariable.

public class ThreadLocalTest {
    // 1 Create the ThreadLocal variable
    private static ThreadLocal<String> localVariable = new ThreadLocal<>();     
    // 2 Create thread one
    public static void main(String[] args) {
        Thread threadOne = new Thread(() -> {
            // 2.1 Set the value of localVariable in thread One
            localVariable.set("threadOne local variable");
            // 2.2 Print the value of localVariable in the local memory of the current thread
            System.out.println("threadOne: " + localVariable.get());
            // 2.3 Print local variables
            System.out.println("threadOne remove after" + ":" + localVariable.get());
        });
        // 3 Create thread two
        Thread threadTwo = new Thread(() -> {
            // 3.1 Set the value of localVariable in thread two
            localVariable.set("threadTwo local variable");
            // 3.2 Print the value of localVariable in the local memory of the current thread
            System.out.println("threadTwo: " + localVariable.get());
            // 3.3 Print local variables
            System.out.println("threadTwo remove after" + ":"+ localVariable.get()); }); threadOne.start(); threadTwo.start(); }}Copy the code

The running results are as follows:

threadOne: threadOne local variable
threadOne remove after: threadOne local variable
threadTwo: threadTwo local variable
threadTwo remove after: threadTwo local variable
Copy the code

As you can see, the calling thread uses the set method to set the value of localVariable (in effect, setting a copy of the calling thread’s local memory, as described in the next section), which is not accessible to other threads.


2.2 ThreadLocal Usage Scenarios

If you write CURD, you will rarely use ThreadLocal:

  1. Use ThreadLocal to wrap some thread-unsafe utility classes such as Random, SimpleDateFormat, and so on.
  2. Using ThreadLocal in conjunction with AOP, for example, sets some important information about the current thread in a pre-notification and gets it in a post-notification.
  3. Connections in the database Connection pool are managed by ThreadLocal, ensuring that multiple DAO operations in a thread all use the same Connection to ensure transactions.
  4. Spring can realize in the multi-threaded environment, the request of each thread is isolated, and accurately injected, the secret is ThreadLocal.

3 Implementation principle of ThreadLocal

Before talking about the principle, I would like to post a very clear description of the structure of the gold mine (solid line is a strong reference, dotted line is a weak reference) :

Each Thread object maintains a member variable (the threadLocals field) of type ThreadLocalMap. Inside the ThreadLocalMap is an Entry table. Entry is a key-value structure where key is a ThreadLocal and value is a stored value. From this we know that each thread stores thread-independent values by virtue of a hash table.

Before delving into the source code, let me give you a brief overview of what ThreadLocal looks like: ThreadLocal is really just a utility class that initializes a ThreadLocalMap object for the thread’s threadLocals field when the thread first calls its set/get method. When a thread calls the set method, it actually puts the value in threadLocals of the calling thread. When a thread calls the get method, it is actually obtained from threadLocals of the calling thread. If the calling thread does not terminate, the local variable will remain in the calling thread’s threadLocals, so it is better to remove it from the calling thread’s threadLocals by calling the remove method when the local variable is no longer needed.

That’s the basics of ThreadLocal, but to get a better understanding of the internal implementation of ThreadLocal, go back to the source code. The next section will be divided into two parts: ThreadLocal and ThreadLocalMap. I chose to give priority to ThreadLocalMap, which is a custom map in ThreadLocal. The ThreadLocal method is actually a simple tool shell, and once you understand the implementation of ThreadLocalMap, the shell is actually not difficult to understand.

3.1 Analysis of ThreadLocalMap source code

The fields and methods of ThreadLocalMap are described below, and the descriptions of some basic fields are posted directly in the code:

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<? >>{ Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}/** Initial size of the Entry array */
    private static final int INITIAL_CAPACITY = 16;    
    /** Entry array */
    private Entry[] table;
    /** Record the actual number of entries in the map */
    private int size = 0;
    /** Specifies the capacity expansion threshold. If size reaches the threshold, resize the entire map */
    private int threshold; 

    /** Expansion threshold based on length = len x 2/3 x /
    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }

    /** Get the next index, return 0 if the length exceeds. You can see that the entry array is actually a ring structure */
    private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }
    /** Return the previous index. If -1 is negative, return the index of length -1. You can see that the entry array is actually a ring structure */
    private static int prevIndex(int i, int len) {
        return ((i - 1> =0)? i -1 : len - 1);
    }

    /** constructor 1: initializes the first Entry value */ simultaneouslyThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) {// The initial capacity is 16
        table = new Entry[INITIAL_CAPACITY];
        // Determine the position of the key-value pair by calculating the index of threadLocal's hashcode & (table.length-1)
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        // Create a new node and store it in the table
        table[i] = new Entry(firstKey, firstValue);
        // Initialize size
        size = 1;
        // Initialize the capacity expansion threshold
        setThreshold(INITIAL_CAPACITY);
    }

    /** ThreadLocal itself is thread isolated, so there is no sharing or passing of data. InheritableThreadLocal provides a mechanism for sharing data between parents and children. The implementation principle is described in section 5 */
    private ThreadLocalMap(ThreadLocalMap parentMap) {
        Entry[] parentTable = parentMap.table;
        int len = parentTable.length;
        setThreshold(len);
        table = new Entry[len];

        for (int j = 0; j < len; j++) {
            Entry e = parentTable[j];
            if(e ! =null) {
                @SuppressWarnings("unchecked")
                ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                if(key ! =null) {
                    Object value = key.childValue(e.value);
                    Entry c = new Entry(key, value);
                    int h = key.threadLocalHashCode & (len - 1);
                    while(table[h] ! =null) h = nextIndex(h, len); table[h] = c; size++; }}}}// Cut it out and save it for later...
Copy the code

The fields and constructors defined in ThreadLocalMap are listed above. The most important concern here is the Entry structure:

  1. Why is an Entry declared as a WeakReference?
  2. ThreadLocalMap entries do not use a linked list structure. How does that resolve hash conflicts?

3.1.1 Why is an Entry Declared as a WeakReference?

Just think, if the declaration is not a WeakReference, then the strong reference is adopted. During the run of a thread, if a ThreadLocal is set to null because it is no longer used, but because it is referenced in the thread’s threadLocals, it cannot be collected by GC (unless the thread ends), which can result in a memory leak. When the Entry is declared as a WeakReference, when a ThreadLocal is set to null, the key in the threadLocalMap in the thread is not a strong reference, and the ThreadLocal can be reclaimed by GC. At the same time, many methods inside threadLocalMap gradually clean up stale entries to avoid memory leaks.

3.1.2 ThreadLocalMap entries do not use a linked list structure. How does that resolve hash conflicts?

HashMap and ThreadLocalMap hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash hash

  • Open address method: easy to produce accumulation problems; Not suitable for large-scale data storage; The design of the hash function has a big impact on collisions; Multiple conflicts may occur during the insertion. The deleted element is one of the conflicting elements, and the subsequent elements need to be processed, which is complicated to implement. When a node is very large, it wastes a lot of space;
  • Chain address method: simple to deal with conflicts, and no accumulation phenomenon, the average search length is short; The nodes in the linked list are applied dynamically, which is suitable for the situation where the length of the list cannot be determined. Relatively speaking, the pointer field of the zipper method can be ignored, so it is more space-saving than the open address method. The insertion node should be at the beginning of the chain. It is convenient to delete the node. It only needs to adjust the pointer without adjusting other conflicting elements.

The open address method does not create a linked list. When the element to which a keyword is hashed is already occupied by another keyword, it attempts to find other elements in the array until an empty element is found. There are many ways to probe for empty cells in an array, and ThreadLocalMap uses the simplest linear probe. If you go to the end of the array and search again from the beginning (ring search), the formula is fi(key)=(f(key)+di) MOD m (di= 1,2,3,…). M −1) Fi (key)=(f(key)+di) MOD m (di=1, 2, 3,…, m−1)

To resolve hash table conflicts ThreadLocal introduces a magic hash code: For details, see the article “ThreadLocal and the magic number 0x61C88647,” but in practice, no matter how you construct hash functions, collisions are inevitable, so here are some other methods of ThreadLocalMap. Let’s see how ThreadLocalMap solves these problems in detail.

3.1.3 getEntry and getEntryAfterMiss methods

This is how to retrieve an Entry node from a ThreadLocalMap. Let’s look at the implementation details:

private Entry getEntry(ThreadLocal
        key) {
    // 1 Use hashcode % (table.length-1) to determine the subscript position
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    // 2 If the keys are the same, return the entry directly
    if(e ! =null && e.get() == key)
        return e;
    else
        // 3 If you cannot find the entry, you need to start from the position I and iterate backwards. Based on the linear detection method, it is possible to find the corresponding entry after I
        return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<? > key,int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
    // 1 If the entry is not empty, it indicates that there is a hash collision and the loop must be traversed backward
    while(e ! =null) {
        // 2 Obtain the key corresponding to the nodeThreadLocal<? > k = e.get();// 3 Return entry if keys are equal
        if (k == key)
            return e;
        // 4 If the key is null, a consecutive segment cleanup is triggered
        if (k == null)
            expungeStaleEntry(i);
        // 5 Get the next subscript position and then judge
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}
Copy the code

Because ThreadLocalMap uses the open address method, in the event of a conflict, the element to be inserted is placed at the null following the insertion position. Therefore, there may not be an Entry exactly equal to the key we want in the subscript position calculated the first time, so we use the linear probe method to find whether it is inserted behind. Notice that when a linear probe into getEntryAfterMiss is performed, if the obtained key is null, the expungeStaleEntry method (which is heavily used in ThreadLocalMap) is triggered for a continuous cleanup. So why is this cleaning needed? Because for threads, the isolated local variables use WeakReference, then it is possible to be recycled when GC, if many Entry nodes have been recycled, but the table array still leaves the position, it is not clean up is a waste of resources. At the same time of node cleaning, the subsequent non-empty Entry nodes can be rewritten to calculate the subscript and discharged. In this way, resources can be quickly located during GET and efficiency can be accelerated.

3.1.4 expungeStaleEntry method

Let’s dive into the expungeStaleEntry source code to see how it is cleaned up:

/** Clean up consecutive segments */
private int expungeStaleEntry(int staleSlot) {
    // 1 Open a new reference to the table
    Entry[] tab = table;
    int len = tab.length;

    // 2 Set the subscript of the table to NULL and set the size of the table to -1
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;

    Entry e;
    int i;
    // 3 Iterate over all subsequent nodes where ThreadLocal is reclaimedTraverse all subsequent nodes where ThreadLocal is reclaimedfor(i = nextIndex(staleSlot, len); (e = tab[i]) ! =null;
            i = nextIndex(i, len)) {
        // 4 Obtain the key in the entryThreadLocal<? > k = e.get();// 5 If the key in the entry is empty, then set the position of value and array index to null, size -1
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            // 6 If it is not empty, recalculate the index of the key
            int h = k.threadLocalHashCode & (len - 1);
            // 7 If it is the current position, the next position is traversed directly; if it is not the current position, the next coordinate is found from I and the next value is null
            if(h ! = i) { tab[i] =null;

                // Unlike Knuth 6.4 Algorithm R, we must scan until
                // null because multiple entries could have been stale.
                while(tab[h] ! =null) h = nextIndex(h, len); tab[h] = e; }}}return i;
}
Copy the code

Obviously, this method is actually doing a clean and rehash operation from staleSlot.

3.1.5 set method

After introducing the method of obtaining an Entry node from a ThreadLocalMap, let’s look at the set method. Naturally, we need to save our variables into a ThreadLocalMap.

private void set(ThreadLocal
        key, Object value) {
    // 1 Open a new reference to the table
    Entry[] tab = table;
    // 2 Obtain the length of the table
    int len = tab.length;
    // 3 Use hashcode % (table.length-1) to determine the subscript position
    int i = key.threadLocalHashCode & (len-1);

    // 4 Iterates from this subscript
    for(Entry e = tab[i]; e ! =null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<? > k = e.get();// 4.1 If the same key is encountered, replace value directly
        if (k == key) {
            e.value = value;
            return;
        }
        // 4.2 If the key is null, the current key is GC as a weak reference, and the old data needs to be cleaned up
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return; }}// 5 Iterates to the position where the array is null and assigns a value
    tab[i] = new Entry(key, value);
    int sz = ++size;
    // 6 Call cleanSomeSlots to try to find and clean up invalid entries. If nothing is found and the current capacity exceeds the threshold, call Rehash
    if(! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }Copy the code

The set method of ThreadLocalMap is actually quite complex, and we can see how the hash table resolves conflicts using linear probing. First look at the replaceStaleEntry method:

3.1.6 replaceStaleEntry method

The replaceStaleEntry method is called when we do a linear probe if we encounter an element with a null key. The function of this method is as follows: we find an element whose key is null in staleSlot, Overwrite the new value to the staleSlot position and clean up elements with null key near staleSlot (i.e. non-null expired elements immediately before and after the staleSlot position) :

private void replaceStaleEntry(ThreadLocal<? > key, Object value,int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    Entry e;

    // 1 Record the index of the first failed node
    int slotToExpunge = staleSlot;
    // 2 Iterates through the failed entry nodes and assigns the node subscript to slotToExpunge if found
    for (inti = prevIndex(staleSlot, len); (e = tab[i]) ! =null;
            i = prevIndex(i, len))
        if (e.get() == null)
            slotToExpunge = i;
    // 3 Iterate backwards from staleSlot
    for (inti = nextIndex(staleSlot, len); (e = tab[i]) ! =null; i = nextIndex(i, len)) { ThreadLocal<? > k = e.get();// 4 Because we call the set method as soon as it touches dirty data, but the actual key may exist in the post position, so it may enter this code
        if (k == key) {
            e.value = value;
             // 4.1 At this time, the found entry is exchanged with the failed node to maintain the order of the hash table
            tab[i] = tab[staleSlot];
            tab[staleSlot] = e;


            // 4.2 If the failed node has not been found before, only the I position is the failed node
            if (slotToExpunge == staleSlot)
                slotToExpunge = i;

            // 4.3 Clean sequential segments first, then call cleanSomeSlots for heuristic cleaning, its time complexity O(log2n)
            cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
            return;
        }

        // 5 If no failed node has been found forward (or backward) and the current subscript is failed node, replace the current node as failed node
        if (k == null && slotToExpunge == staleSlot)
            slotToExpunge = i;
    }

    // 6 If the current table does not find the key, create a new Entry at staleSlot
    tab[staleSlot].value = null;
    tab[staleSlot] = new Entry(key, value);

    // 7 If an invalid entry node is found in the previous loop probe, that is, slotToExpunge has been reassigned, sequential segment cleaning and heuristic cleaning will be triggered
    if(slotToExpunge ! = staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }Copy the code

3.1.7 cleanSomeSlots method

The expungeStaleEntry method, introduced earlier, does a continuous segment cleanup and rehash operation from staleSlot, while the cleanSomeSlots method does a heuristic cleanup, and its execution complexity log2(n), This method just tries to find some invalid entries and returns true if any invalid entries are found.

private boolean cleanSomeSlots(int i, int n) {
    boolean removed = false;
    Entry[] tab = table;
    int len = tab.length;
    do {
        i = nextIndex(i, len);
        Entry e = tab[i];
        // 1 If the node is invalid
        if(e ! =null && e.get() == null) {
            n = len;
            removed = true;
            // 2 Calls this method for continuous collection and rehash
            i = expungeStaleEntry(i);
        }
    // n >>>= 1 unsigned right shift 1, that is, the number of moves is based on the position of the highest binary bit of n, so the time complexity log2(n)
    } while ( (n >>>= 1) != 0);
    return removed;
}
Copy the code

After successfully adding or replacing elements, in order to minimize the number of old elements found in get/set, cleanSomeSlots is called several times to try to find and clean some old elements after clearing expired entries. For execution efficiency, “CleanSome” is a balance between “no clean” and “clean all”.

3.1.8 Rehash/expungeStaleEntries/resize method

After the set is successful, the threshold determines that if the table space is insufficient, the rehash method is called, which first fully traverses to clear the failed nodes, then clears the failed nodes to determine whether the capacity is sufficient, and if not, doubles the capacity and rehashes it. ExpungeStaleEntries means full cleanup of old elements, and resize means double expansion.

/** Rehash traverses the old elements in full, then determines if the capacity is greater than 3/4 of the threshold, expands and hashes */
private void rehash(a) {
    // 1 Clean up old elements by full traversal
    expungeStaleEntries();

    // 2 Expand appropriately to avoid too many position conflicts when hashing into the array
    if (size >= threshold - threshold / 4)
        // 2.1 Expand by 2x and rehash
        resize();
}

/** Double capacity expansion */
private void resize(a) {
    // 1 Obtain the length of the old table and create an Entry array twice as long as the old one
    Entry[] oldTab = table;
    int oldLen = oldTab.length;
    int newLen = oldLen * 2;
    Entry[] newTab = new Entry[newLen];
    // 2 Record the number of valid Entry nodes inserted
    int count = 0;

    // 3 Insert into the new table one by one, starting with index 0
    for (int j = 0; j < oldLen; ++j) {
        Entry e = oldTab[j];
        if(e ! =null) { ThreadLocal<? > k = e.get();// 4 If the key is null, set value to NULL to facilitate GC collection
            if (k == null) {
                e.value = null; // Help the GC
            } else {
                // 5 Calculates the subscript with hashcode & Len-1, and if there is already an Entry array at that position, it is inserted backwards with a linear probe
                int h = k.threadLocalHashCode & (newLen - 1);
                while(newTab[h] ! =null) h = nextIndex(h, newLen); newTab[h] = e; count++; }}}// 6 Reset the capacity expansion threshold
    setThreshold(newLen);
    // 7 Update size
    size = count;
    // 8 points to the new Entry array
    table = newTab;
}

/** Clean up old elements */
private void expungeStaleEntries(a) {
    Entry[] tab = table;
    int len = tab.length;
    for (int j = 0; j < len; j++) {
        Entry e = tab[j];
        if(e ! =null && e.get() == null) expungeStaleEntry(j); }}}Copy the code

3.1.9 remove method

If the storage is in Map form, of course, it is necessary to remove the method:

/** Remove Entry nodes from the table */
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) {
            // Set the reference to NULL for GC convenience
            e.clear();
            // Start a continuous segment cleanup from this position
            expungeStaleEntry(i);
            return; }}}Copy the code

As you can see, a linear probe is also used when removing a node. When the corresponding key is found, clear is called to point the reference to null, triggering a continuous segment cleanup.


3.2 ThreadLocal source analysis

If you understand the structure of a ThreadLocalMap, then understanding a ThreadLocal is not a problem. In my opinion, a ThreadLocal simply encapsulates the utility methods that operate on a ThreadLocalMap. The following sections describe ThreadLocal implementations one by one.

3.2.1 the get method

You can see that the execution process of the get method is: If threadLocals is not empty, obtain the entry object from the current Thread. If threadLocals is not empty, obtain the entry object from the current Thread. Then take the value from the entry. If threadLocals is empty, then setInitialValue is called to initialize.

public T get(a) {
    // 1 Get the current thread
    Thread t = Thread.currentThread();
    // 2 Get the threadLocals variable for the current thread
    ThreadLocalMap map = getMap(t);
    // 3 If threadLocals is not empty, return the value of the corresponding local variable
    if(map ! =null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if(e ! =null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            returnresult; }}// 4 If threadLocals is empty, initialize the threadLocals member variable for the current thread
    return setInitialValue();
}

// We can see that the getMap method returns the threadLocals variable of the current Thread object.
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

// The setInitialValue method is called when threadLocals is empty
// It first calls the initialValue method to generate a null value
// Then check if threadLocals is null. If not, set value.
// If it does not exist, call createMap to create the threadLocals variable for the current thread.
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;
}
// The constructor of ThreadLocalMap is called to initialize the map object for the threadLocals field
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

Copy the code

3.2.1 set method

You can see that the set method gets the current calling thread first, and then the threadLocals variable from the calling thread via the getMap method. If threadLocals is not empty, the value is set to threadLocals. If threadLocals is empty, the threadLocals variable for the current thread is created.

public void set(T value) {
    // 1 Get the current thread
    Thread t = Thread.currentThread();
    // 2 Take the current thread as the key, look for the corresponding thread variable, find it, set, if the first call to create the current thread corresponding to the hashMap
    ThreadLocalMap map = getMap(t);
    if(map ! =null)
        map.set(this, value);
    else
        createMap(t, value);
}

// Source code:
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Copy the code

4 ThreadLocal does not support inheritance

Let’s start with some code:

public class TestThreadLocal {

    // 1 Create the thread variable
    public static ThreadLocal<String> threadLocal1 = new ThreadLocal<>();

    public static void main(String[] args) {
        // 2 Set thread variables
        threadLocal1.set("hello world");
        // 3 Start the child thread
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run(a) {
                // 4 The child thread outputs the value of the thread variable
                System.out.println("thread: "+ threadLocal1.get()); }}); thread.start();// 5 The main thread outputs the values of thread variables
        System.out.println("main: "+ threadLocal1.get()); }}Copy the code

The running results are as follows:

main: hello world
thread: null
Copy the code

As you can see, the same ThreadLocal variable that has been set in the parent thread cannot be obtained in the child thread. This is normal, and you can use the InheritableThreadLocal variable to resolve this if you want.


5 InheritableThreadLocal class

5.1 How InheritableThreadLocal works

To solve the problem that threadLocal does not support inheritance, which means that a child thread needs to have access to values in the parent thread, we need to use InheritableThreadLocal.

package java.lang;
import java.lang.ref.*;

public class InheritableThreadLocal<T> extends ThreadLocal<T> {

    protected T childValue(T parentValue) {
        return parentValue;
    }

    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); }}Copy the code

As you can see, InheritableThreadLocal inherits ThreadLocal and overwrites all three methods. The craeteMap method creates an instance of inheritableThreadLocals for the current thread. Not threadLoacals. When you call get to get a map variable inside the current thread, you get inheritableThreadLocals instead of threadLoacals. So to see how InheritableThreadLocal allows a child to access its parent’s local variables, we need to look at childValue. Before we do that, let’s start with the construction of Thread:

private void init(ThreadGroup g, Runnable target, String name,
                    long stackSize, AccessControlContext acc,
                    boolean inheritThreadLocals) {
    if (name == null) {
        throw new NullPointerException("name cannot be null");
    }
    // 1 Specifies the thread name
    this.name = name;

    // 2 Get the current thread
    Thread parent = currentThread();
    // 3 Obtain the system security service
    SecurityManager security = System.getSecurityManager();
    // 4 If there is no thread group, set the thread group. If the value is NULL, obtain the thread group from the security service. If the obtained thread group is still null, obtain the thread group of the current thread
    if (g == null) {
        if(security ! =null) {
            g = security.getThreadGroup();
        }
        if (g == null) { g = parent.getThreadGroup(); }}// 5 Check the thread group
    g.checkAccess();
    // 6 Check permissions
    if(security ! =null) {
        if(isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); }}// 7 Add threads to the thread group
    g.addUnstarted();

    this.group = g;
    this.daemon = parent.isDaemon();
    this.priority = parent.getPriority();
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext = acc ! =null ? acc : AccessController.getContext();
    this.target = target;
    setPriority(priority);
    // 8 If the parent thread's inheritableThreadLocals is not null
    if(inheritThreadLocals && parent.inheritableThreadLocals ! =null)
    // 9 Set the inheritableThreadLocals variable in the child thread
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    this.stackSize = stackSize;
    tid = nextThreadID();
}
Copy the code

The init method is called when a thread is created. It takes the current thread (the parent) and determines if the parent’s inheritableThreadLocals variable is null. CreateInheritedMap creates a new ThreadLocalMap variable and assigns the child thread’s inheritableThreadLocals variable:

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}

private ThreadLocalMap(ThreadLocalMap parentMap) {
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    setThreshold(len);
    table = new Entry[len];

    for (int j = 0; j < len; j++) {
        Entry e = parentTable[j];
        if(e ! =null) {
            @SuppressWarnings("unchecked")
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if(key ! =null) {
                Object value = key.childValue(e.value);
                Entry c = new Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while(table[h] ! =null) h = nextIndex(h, len); table[h] = c; size++; }}}}Copy the code

You can see that the constructor internally copies the values of the parent thread’s inheritableThreadLocals member variables into the new ThreadLocalMap (by calling the childValue method overridden by the inheritableThreadLocals class).

So inheritableThreadLocals works by overriding getMap and createMap so that local variables are saved to inheritableThreadLocals in the thread itself. When the parent creates the child function, The constructor makes a copy of the parent’s inheritableThreadLocals to the child’s inheritableLocals.

5.2 Basic use of InheritableThreadLocal

public class TestThreadLocal2 {

    // 1 Create the thread variable
    public static ThreadLocal<String> threadLocal1 = new InheritableThreadLocal<>();

    public static void main(String[] args) {
        threadLocal1.set("hello world");
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run(a) {
                System.out.println("thread: "+ threadLocal1.get()); }}); thread.start(); System.out.println("main: "+ threadLocal1.get()); }}Copy the code

The running results are as follows:

main: hello world
thread: hello world
Copy the code

5.3 Use of InheritableThreadLocal

InheritableThreadLocal is not irreplaceable; for example, you can construct a map in the parent thread and pass it to the child thread as an argument. For example, the child thread needs to use the user login information in the parent thread’s threadLocal variable. For example, some middleware needs to record the entire call link with a unified ID trace.


6 References

As you can see, the logic of ThreadLocal code is very rigorous, with only a few hundred lines of code, a large number of exceptions, and a clever connection between local variables and threads. I really admire Doug Lea. Thanks to the hard work of good bloggers found on Google, they are like:

Java interview must ask, ThreadLocal and ThreadLocalMap source code analysis open address method to deal with hash conflict hash table: separation of linked list method and open addressing method Java advanced (7) correct understanding of Thread Principles and application scenarios of Local