This article summarizes ThreadLocal, and the main topics are:

  1. ThreadLocal is introduced
  2. How ThreadLocal works
  3. ThreadLocal memory leak analysis
  4. Application scenarios and Examples of ThreadLocal

I first heard about ThreadLocal when I was an intern in ’18. At that time, THERE was a task that required a thread pool. Some people said that the problem of concurrency could be solved with ThreadLocal. But it was not used at the time, leaving a vague impression that it could be used to solve concurrency problems.

I still use ThreadLocal in my projects, but it’s a bit vague to explain how it works in detail, so I took a look at the source code for ThreadLocal in my spare time to share my understanding of it and my imagination.

This article source is based on JDK1.8

1. The ThreadLocal is introduced

As stated in the JDK comment: “The ThreadLocal class provides thread-local variables, which are typically static fields in a private class that you want to associate state with a thread.”

In short, ThreadLocal provides data isolation between threads, and its name indicates that this is a local variable belonging to a thread. That is, each thread keeps a copy of its own data in ThreadLocal, so it is thread-safe.

Those familiar with Spring probably know the Scope of a Bean, and the Scope of a ThreadLocal is a thread.

We can use a simple example to illustrate the features of ThreadLocal:

public static void main(String[] args) { ThreadLocal<String> threadLocal = new ThreadLocal<>(); ExecutorService threadPool = new ThreadPoolExecutor(2, 2, 1, timeunit.milliseconds, new LinkedBlockingQueue<>(10)); Threadpool.execute (() -> threadlocal.set (" task 1: " + Thread.currentThread().getName())); Threadpool.execute (() -> threadlocal.set (" thread.currentThread ().getName()) "); Threadpool.execute (() -> threadlocal.set (" thread.currentThread ().getName()) "); For (int I = 0; i < 10; i++) { threadPool.execute(() -> System.out.println("ThreadLocal value of " + Thread.currentThread().getName() + " = " + threadLocal.get())); } // Remember to close threadpool.shutdown (); }Copy the code

The above code first creates a normal thread pool with two core threads, then submits a task, puts the task serial number and the thread name of the child thread executing the task into a ThreadLocal, and finally prints the values stored in the ThreadLocal for each thread in the pool in a for loop. The output of this program is:

Value of ThreadLocal - thread pool - 1-1 = task 3: ThreadLocal - thread pool - 1-1 the value of the pool - 1 - thread - 2 = task 2: Pool-1-thread-2 ThreadLocal value of pool-1-thread-1 = task 3: pool-1-thread-1 ThreadLocal value of pool-1-thread-2 = task 2: Pool-1-thread-2 ThreadLocal value of pool-1-thread-1 = task 3: pool-1-thread-1 ThreadLocal value of pool-1-thread-2 = task 2: Pool-1-thread-2 ThreadLocal value of pool-1-thread-1 = task 3: pool-1-thread-1 ThreadLocal value of pool-1-thread-2 = task 2: Pool-1-thread-2 ThreadLocal value of pool-1-thread-1 = task 3: pool-1-thread-1 ThreadLocal value of pool-1-thread-2 = task 2: pool-1-thread-2Copy the code

ThreadLocal = pool-1-thread-1; pool-1-thread-1; pool-1-thread-1; pool-1-thread-1; What each thread stores in a ThreadLocal is unique to the current thread, preventing its variables from being modified by other threads in a multi-threaded environment (except when the contents are stored in objects of the same reference type).

2. Implementation principle of ThreadLocal

In JDK1.8, the ThreadLocal class has 723 lines of code, with 350 lines removed from the comments. It is relatively easy to understand the source code of the core JDK class.

Let’s start with ThreadLocal’s data structure and talk about how it works.

2.1 Underlying data structure

First open the chapter name: ThreadLocalMap is a static inner class that stores data. ThreadLocalMap is a Map of key-value pairs. The underlying class is an array of Entries. The keys in the Entry Object are ThreadLocal objects, and the values are concrete stores of type Object.

ThreadLocalMap is also an attribute of the Thread class.

How to prove the correctness of the underlying data structure of the ThreadLocal class given above? We can start tracing the code from the ThreadLocal#get() method to see exactly where the thread-local variable is fetched from.

Public T get() {T = thread.currentThread (); / / get the Thread class ThreadLocal. ThreadLocalMap type threadLocals variable ThreadLocalMap map = getMap (t); If (map! = null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e ! = null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } // If the threadLocals variable is NULL, initialize a new ThreadLocalMap object. Return setInitialValue(); } // initialize a new ThreadLocalMap object private T setInitialValue() {// initialize a NULL T value = initialValue(); Thread t = thread.currentThread (); / / get the Thread class ThreadLocal. ThreadLocalMap type threadLocals variable ThreadLocalMap map = getMap (t); if (map ! = null) map.set(this, value); else createMap(t, value); return value; } // ThreadLocalMap#createMap void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }Copy the code

The ThreadLocal#get() method makes it clear that when we read data from a ThreadLocal object, we first fetch the current thread object, And then get the current thread object ThreadLocal. ThreadLocalMap threadLocals attribute types, if threadLocals attribute is not null, The value of the key is obtained based on the ThreadLocal object as the key. If the threadLocals variable is NULL, a new ThreadLocalMap object is initialized.

Look at ThreadLocalMap constructor, which is the Thread class ThreadLocal. ThreadLocalMap type of execution logic threadLocals attribute is not null.

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); }Copy the code

This constructor takes the ThreadLocal Object as the key, the Object as the value, wraps it into an Entry Object, and places it in the Table property of the Entry array in the ThreadLocalMap class. This completes the storage of thread-local variables.

So the data in ThreadLocal is ultimately stored in the ThreadLocalMap class.

2.2 Hash mode

In ThreadLocalMap# set (ThreadLocal <? > key, Object value);

Int I = key.threadLocalHashCode & (len-1); int I = key.threadLocalHashCode & (len-1);Copy the code

This line of code is actually a hash of a ThreadLocal object. This is the hash of a ThreadLocal, called Fibonacci hash.

// ThreadLocal#threadLocalHashCode private final int threadLocalHashCode = nextHashCode(); // ThreadLocal#nextHashCode private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } // ThreadLocal#nextHashCode private static AtomicInteger nextHashCode = new AtomicInteger(); // AtomicInteger#getAndAdd public final int getAndAdd(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta); Threadlocal_increment private static final int HASH_INCREMENT = 0x61C88647; threadLocal_increment private static final int HASH_INCREMENT = 0x61C88647;Copy the code

As shown above, the threadLocalHashCode attribute for each ThreadLocal is generated based on the magic number 0x61C88647.

I won’t go into the reasons for choosing this magic number, but a lot of practice shows that threadLocalHashCode generated using 0x61C88647 as a magic number, which is then superimposed on a power of 2, produces a fairly uniform distribution of results.

Mod A % 2^N can be replaced by A & (2^n-1). Bit operation is much more efficient than mod operation.

2.3 How to Resolve Hash Conflicts

We already know that the underlying data structure of ThreadLocalMap is an Entry array, but unlike the Node array + linked list form in HashMap, Entry does not have the next attribute to form a linked list, so it is a pure array.

Even if Fibonacci hashes are sufficient, hash collisions are still possible. So how does Entry resolve hash collisions?

ThreadLocalMap#set(T value) ¶ > key, Object value)

Public void set(T value) {public void set(T value) { / / get the Thread class ThreadLocal. ThreadLocalMap type threadLocals variable ThreadLocalMap map = getMap (t); // If the threadLocals variable is not empty, perform the assignment. Otherwise, create a new ThreadLocalMap object to store if (map! = null) map.set(this, value); else createMap(t, value); } // ThreadLocalMap#set private void set(ThreadLocal<? > key, Object value) {// Obtain the Entry of ThreadLocalMap array Object Entry[] TAB = table; int len = tab.length; Int I = key.threadLocalHashCode & (len-1); 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; } // code (2) if (k == null) {replaceStaleEntry(key, value, I); return; TAB [I] = new Entry(key, value); int sz = ++size; if (! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } // ThreadLocalMap#nextIndex // Entry the nextIndex of the array, starting from 0 if the array size exceeds, Private static int nextIndex(int I, int len) {return ((I + 1 < len)? i + 1 : 0); }Copy the code

ThreadLocalMap#set(ThreadLocal<? > key, Object value), then we get the hash value of the ThreadLocal Object and enter a for loop. Starting at the hash value of the ThreadLocal object of the Entry array, it moves backward one bit at a time. If the size of the array exceeds, the loop continues from 0 until the Entry object is NULL.

In the cycle:

  • As shown in code (1), if the current ThreadLocal object is exactly equal to the key attribute in the Entry object, the value of the ThreadLocal is directly updated.
  • As shown in code (2), if the current ThreadLocal object is not equal to the key attribute in the Entry object, and the key of the Entry object is empty, the logic is to set the key-value pair and clean up the invalid Entry (the program must prevent memory leakage, More on this later);
  • For example, in code (3), if the hash value of the current TheadLocal object is not found in the traversal, and the key of the Entry object is not found to be empty, but the condition of exiting the loop is met, that is, the Entry object is empty, a new Entry object will be created for storage. At the same time, a heuristic cleaning is performed to release the value of the object whose key is empty but value is not empty in the Entry array.

After analyzing the logic of storing data into a ThreadLocal and retrieving the hash value of the ThreadLocal object, we return to the topic of this section — how does a ThreadLocal resolve hash conflicts?

As you can see from the above code, after obtaining the hash value of the current ThreadLocal object based on Fibonacci hash, we enter a loop that deals with the methods that handle hash collisions:

  • If the hash value already exists and the key is the same object, update the value directly
  • If the hash value already exists but the key is not the same object, try to store it in the next empty location

So, to summarize how ThreadLocal handles hash collisions: ** If a set encounters a hash conflict, ThreadLocal will use linear detection to try to store the array at the next index and release the key NULL during the set. Value the value property of a non-null dirty Entry object to prevent memory leaks **.

2.4 Initial Capacity and Expansion mechanism

The constructor of ThreadLocalMap was mentioned above and is explained in detail here.

ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) {// Initialize Entry array table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; SetThreshold (INITIAL_CAPACITY); }Copy the code
  • The initial capacity of ThreadLocalMap is 16

    Private static final int INITIAL_CAPACITY = 16;

ThreadLocalMap (ThreadLocalMap) has two steps to determine whether to expand capacity.

  • ThreadLocalMap#set(ThreadLocal<? > key, Object value) method may trigger heuristic cleaning, after cleaning invalid Entry objects, if the array length is greater than or equal to 2/3 of the array defined length, the first rehash is performed.

    Private void setThreshold(int len) {threshold = len * 2/3; }

  • Rehash triggers a full cleanup, resize if the array length is greater than or equal to half of the array’s defined length.

    Private void rehash() {expungeStaleEntries();

    // Use lower threshold for doubling to avoid hysteresis if (size >= threshold – threshold / 4) resize(); }

  • During capacity expansion, the size of the Entry array is doubled and the hash value of the key is recalculated. If the key is NULL, the value of the key is also NULL to facilitate VM GC.

    Private void resize() {Entry[] oldTab = table; int oldLen = oldTab.length; int newLen = oldLen * 2; Entry[] newTab = new Entry[newLen]; int count = 0;

    for (int j = 0; j < oldLen; ++j) { Entry e = oldTab[j]; if (e ! = null) { ThreadLocal<? > k = e.get(); if (k == null) { e.value = null; // Help the GC } else { int h = k.threadLocalHashCode & (newLen – 1); while (newTab[h] ! = null) h = nextIndex(h, newLen); newTab[h] = e; count++; }}}

    setThreshold(newLen); size = count; table = newTab; }

2.5 How are local variables passed between parent and child threads

We already know that ThreadLocal stores variables that are local to threads. What if we had a requirement to implement local variable passing between threads?

The InheritableThreadLocal class was designed in anticipation of this need.

The InheritableThreadLocal class uses no more than 10 lines of code except comments, because it inherits a lot of things that are already implemented in the ThreadLocal class. The InheritableThreadLocal class overrides only three of these methods:

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

Let’s start with a simple example to practice passing local variables between parent and child threads.

public static void main(String[] args) { ThreadLocal<String> threadLocal = new InheritableThreadLocal<>(); Threadlocal.set (" This is the value set by the parent thread "); New Thread(() -> system.out.println (" threadLocal.get())).start(); } // Output content Child thread output: this is the value set by the parent threadCopy the code

As you can see, the InheritableThreadLocal#get() method is called in the child thread to get the value set in the parent thread.

So how does this work?

Implementing local variable sharing between parent and child threads requires tracing the Thread object’s constructor:

public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } private void init(ThreadGroup g, Runnable target, String name, long stackSize) { init(g, target, name, stackSize, null, true); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, Boolean inheritThreadLocals) {// Omits most of the code Thread parent = currentThread(); / / copy inheritableThreadLocals attribute of the parent thread, the realization of father and son thread-local variables share the if (inheritThreadLocals && parent. InheritableThreadLocals! = null) { this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); } // omit some code}Copy the code

In the constructor that is finally executed, there is a judgment like this: If the inheritableThreadLocals property of the parent thread is not NULL, It copies the inheritableThreadLocals property of the current parent thread to the inheritableThreadLocals property of the child thread. Specific replication methods are as follows:

// ThreadLocal#createInheritedMap 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) {// InheritableThreadLocal#childValue(T parentValue) 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

Note that the time to copy the parent thread’s shared variables is when the child thread is created. If the parent thread sets the content in an InheritableThreadLocal object after the child thread is created, it is no longer visible to the child thread.

3. ThreadLocal memory leak analysis

Finally, there is the memory leak problem with ThreadLocal, which is well known to cause memory leaks if not used properly.

Memory leak refers to the fact that the dynamically allocated heap memory in the program is not released or cannot be released due to some reason, resulting in the waste of system memory, slowing down the running speed of the program, and even system crash.

3.1 Causes of memory leaks

The reason for memory leaks in ThreadLocal starts with the Entry object.

// ThreadLocal->ThreadLocalMap->Entry 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

The key of the Entry object, namely the ThreadLocal class, is inherited from the WeakReference WeakReference class. Objects with weak references have a shorter life cycle, and when GC activity occurs, the garbage collector collects objects with weak references regardless of whether there is sufficient memory space.

Since the key of the Entry object is inherited from the WeakReference WeakReference class, if the ThreadLocal class has no external strong reference, the ThreadLocal object will be reclaimed when GC activity occurs.

If the thread that created the ThreadLocal class is still active, then the value corresponding to the ThreadLocal object in the Entry object will still have a strong reference and will not be reclaimed, resulting in a memory leak.

3.2 How can I Solve the Memory Leak Problem

Fixing a memory leak is as simple as remembering to remove the contents stored in ThreadLocal when you’re done with them.

This is an active way to prevent memory leaks, but the designers of ThreadLocal are aware that they can cause memory leaks, so they have also designed a way to prevent memory leaks.

3.3 How does ThreadLocal prevent memory leaks internally

ThreadLocalMap#set(ThreadLocal key, Object value) ¶ If the key of an Entry array is not the same object as the current ThreadLocal, and the key is empty, invalid entries will be cleared. ThreadLOcalMap#replaceStaleEntry(ThreadLocal key, Object value, int staleSlot)

ThreadLocalMap#set(ThreadLocal<? > key, Object value) replaceStaleEntryCopy the code

If the end condition of the linear detection loop is met, that is, Entry==NULL is encountered, an Entry object is created to store data. A heuristic cleanup is then performed, and ThreadLocalMap#rehash() is executed if the heuristic cleanup fails to free the objects that meet the criteria and the expansion criteria are met.

Private void rehash() {// Full clean expungeStaleEntries(); If (size >= threshold-threshold / 4) resize(); }Copy the code

The ThreadLocalMap#rehash() method performs a full cleanup of the ThreadLocalMap. This cleanup iterates through the entire array of entries and removes all dirty entries with NULL keys and non-null values.

Private void expungeStaleEntries() {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

After full clearance, if the size of the Entry array is greater than or equal to threshold-threshold / 4, the system expands the Entry array by two times.

In summary, memory leaks are avoided by actively cleaning up invalid entries with NULL keys and non-null values in the GET, set, and remove methods.

However, letting ThreadLocal clean up invalid entries based on get and SET methods cannot completely avoid memory leaks. In order to solve memory leaks, we need to get into the habit of calling remove to release resources after using them.

4. Application scenarios and examples of ThreadLocal

ThreadLocal is used in many open source frameworks, such as the implementation of the transaction isolation level in Spring and the implementation of the MyBatis paging plugin PageHelper.

At the same time, I have implemented interface whitelist authentication function based on ThreadLocal and filter in the project.

5. Summary

To summarize what this article describes about ThreadLocal in the form of interview questions:

  • What problems ThreadLocal solves
  • ThreadLocal underlying data structure
  • Hash mode of ThreadLocalMap
  • How does ThreadLocalMap handle hash collisions
  • ThreadLocalMap Capacity expansion mechanism
  • How does ThreadLocal implement local variable sharing between parent and child threads
  • Why does ThreadLocal leak
  • How can a ThreadLocal memory leak be resolved
  • How does ThreadLocal prevent memory leaks internally, and in which methods
  • Application scenario of ThreadLocal

Three things to watch ❤️

If you find this article helpful, I’d like to invite you to do three small favors for me:

  1. Like, forward, have your “like and comment”, is the motivation of my creation.

  2. Follow the public account “Java rotten pigskin” and share original knowledge from time to time.

  3. Also look forward to the follow-up article ing🚀

  4. [666] Scan the code to obtain the learning materials package