This article has participated in the call for good writing activities, click to view: back end, big front end double track submission, 20,000 yuan prize pool waiting for you to challenge!

Multithreaded concurrency is a very important content in Java language, but also a difficult point in Java foundation. It is important because multithreading is frequently used in daily development, and it is difficult because there are so many knowledge points involved in multithreading concurrency that it is not easy to fully master Java concurrency knowledge. For this reason, Java concurrency is one of the most frequently covered topics in Java interviews. This series of articles will take a systematic look at Java concurrency in terms of the Java memory model, volatile keywords, synchronized keywords, ReetrantLock, Atomic concurrency classes, and thread pools. In this series of articles, you will learn more about the use of volatile, the implementation of synchronized, AQS and CLH queue locking, and clearly understand spin locking, bias locking, optimistic locking, pessimistic locking… And so on a dizzying array of concurrent knowledge.

Multi-threaded concurrency series

This time, understand the Java memory model and the volatile keyword once and for all

This time, thoroughly understand the Synchronized keyword in Java

This time, thoroughly understand the Java ReentrantLock implementation principle

This time, understand Java thoroughly and send out the Atomic Atomic classes in the package

Understanding the wait and wake up mechanism of Java threads

Understanding the wait and wake up mechanism of Java threads (Part 2)

Java Concurrency series finale: Get to the bottom of how Java thread pools work

The principle of ThreadLocal is simple

One of the most important problems to solve when multithreading concurrency is the problem of variable synchronization in multithreading shared memory. The previous articles addressed this issue with volatile, synchronized, and ReentrantLock and Atomic classes. There are many cases where you want a variable to be invisible to other threads and only one thread to access it, and ThreadLocal provides this capability. This article is an extension of the Java concurrency series that takes a closer look at ThreadLocal and how it works.

Before I start, I would like to recommend the GitHub repository AndroidNote, which is my study notes and the source of the first draft of my article. This repository contains a lot of Java and Android advancements. Is a systematic and comprehensive Android knowledge base. It is also a valuable interview guide for students preparing for interviews. Welcome to the GitHub warehouse homepage.

1. ThreadLocal basics

You probably don’t use ThreadLocal much in your development, and many of you might think ThreadLocal is trivial. But it turns out that ThreadLocal is far more important than we realize. Android developers are probably familiar with Looper in Android messaging. The underlying implementation of Looper relies on ThreadLocal. However, the operation of Android system is driven by Message. The core of Message driver is Handler and Looper, which means that Looper supports the operation of the entire Android system. Spring uses ThreadLocal to ensure that database operations in a single thread use the same database connection. ThreadLocal plays an important role in the Java architecture.

1. The use of ThreadLocal

ThreadLocal is a generic class. Generics represent the types that ThreadLocal can store, and it’s very simple to use. For example, use ThreadLcoal to store a number in a child thread, and then retrieve the value in the child thread and main thread, as follows:


public static void main(String[] args) {
    ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

    new Thread(() -> {
        threadLocal.set(10);
        try {
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " value = " + threadLocal.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();

    try {
        Thread.sleep(1000);
        System.out.println(Thread.currentThread().getName() + " value = " + threadLocal.get());
    } catch(InterruptedException e) { e.printStackTrace(); }}Copy the code

The above code is printed as follows:

Thread-0 value = 10
main value = null
Copy the code

As you can see, we store a 10 in the child thread through ThreadLocal, and the child thread can fetch that value. The main thread gets null. This means that data stored via ThreadLocal by a thread can only be accessed by that thread.

In addition, ThreadLocal can set the global initial value as follows:

ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 10);
Copy the code

ThreadLocal withInitial specifies an initial value of 10 from the child thread and the main thread.

Thread-0 value = 10
main value = 10
Copy the code

In addition to the set and GET methods, ThreadLocal also provides the remove method, which is easy to use without listing the code here.

How ThreadLocal works

How exactly does a ThreadLocal store data that is visible only to the thread that sets it? To understand why, we need to look at how ThreadLocal is implemented.

Let’s start with ThreadLocal’s set method.

1. Set procedure of ThreadLocal

Set method source is relatively simple, as follows:

public void set(T value) {
    // Get the current thread
    Thread t = Thread.currentThread();
    // Get the ThreadLocalMap in the thread
    ThreadLocalMap map = getMap(t);
    if(map ! =null) {
        // Store values in ThreadLocalMap
        map.set(this, value);
    } else {
        // Create a ThreadLocalMap and store the values
        createMap(t, value);
    }
    
void createMap(Thread t, T firstValue) {
    // Instantiate ThreadLocalMap in the current thread
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Copy the code

The above code first fetches the current thread and then fetches a ThreadLocalMap from the current thread, which is a collection of k-V stores that we’ll examine later. If ThreadLocalMap is not empty, the value is stored in the current thread’s corresponding ThreadLocalMap using ThreadLocalMap’s set method. If ThreadLocalMap is empty, ThreadLcoalMap is created and the value is stored in ThreadLocalMap. Also, notice that the key for ThreadLocalMap is the current ThreadLocal.

2. Get of ThreadLocal

Let’s look at how to retrieve data from a ThreadLocal. The code for the get method looks like this:

public T get(a) {
    // Get the current thread
    Thread t = Thread.currentThread();
    // Get the ThreadLocalMap of the current thread
    ThreadLocalMap map = getMap(t);
    if(map ! =null) {
        // Fetch the value from ThreadLocalMap
        ThreadLocalMap.Entry e = map.getEntry(this);
        if(e ! =null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            returnresult; }}// Return the initial value if the value is null
    return setInitialValue();
}
// Set the initial value for ThreadLocal
private T setInitialValue(a) {
    // The initial value is null
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map ! =null) {
        map.set(this, value);
    } else {
        / / create ThreadLocalMap
        createMap(t, value);
    }
    if (this instanceofTerminatingThreadLocal) { TerminatingThreadLocal.register((TerminatingThreadLocal<? >)this);
    }
    return value;
}
// The initial value is null
protected T initialValue(a) {
    return null;
}
Copy the code

As you can see, the get method gets the current thread first, then gets the current thread’s ThreadLocalMap, and values this ThreadLocal as the key through ThreadLocalMap’s getEntry method. If ThreadLocalMap is null, a null value is returned via the setInitialValue method.

In general, the set method puts the value into the current thread’s ThreadLocalMap, and the key is the current ThreadLocal. The GET method retrieves the ThreadLcoalMap in the current thread and uses this ThreadLocal as the key to fetch the value. At this point we can actually explain why a value in a ThreadLocal is only visible to the thread that sets it. But it’s a bit like missing the forest for the trees. After all, what is ThreadLocalMap?

3.ThreadLocalMap

As we’ve seen in the previous two sections, ThreadLocalMap is a data structure that stores type K-V, and a ThreadLocalMap member is maintained in the Thread class. The code is as follows:

public class Thread implements Runnable {

    ThreadLocal.ThreadLocalMap threadLocals = null; . }Copy the code

ThreadLocalMap is an inner class of ThreadLocal. ThreadLocalMap has the following class structure:

static class ThreadLocalMap {

    private Entry[] table;
    private int size = 0;
    
}
Copy the code

ThreadLocalMap maintains an array of entries and a size of type int. Entry is the inner class of ThreadLocalMap, which encapsulates the value we set as follows:

static class Entry extends WeakReference<ThreadLocal<? >>{ Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}Copy the code

It can be seen that the structure of Entry class is very simple. It inherits WeakReference and maintains an Object type value internally. WeakReference maintains a referent member, which refers to ThreadLocal in Entry. That is, the Entry maintains a ThreadLocal as the key and an Object value as the value.

Next, look at the set method of ThreadLocalMap.

private void set(ThreadLocal
        key, Object value) {


    Entry[] tab = table;
    int len = tab.length;
    // Get the key hash value as the position in the Entry array
    int i = key.threadLocalHashCode & (len-1);
    // A hash conflict occurs, which is handled by linear probing and hashing
    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) {
            replaceStaleEntry(key, value, i);
            return; }}// Encapsulate the key and value into an Entry array
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if(! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }Copy the code

ThreadLocalMap is a hash table structure, as shown in the set method of ThreadLocalMap. The set method is an operation that inserts a value into a hash table. As we know, hash conflicts occur in hash tables. Therefore, the above code first uses linear detection and hash method to handle hash conflicts, and then encapsulates value into Entry and inserts it into the Entry array. If you are not familiar with hash tables and hash conflicts, you can refer to my previous article “Interviewers: How do you understand HashMaps when you don’t know Hashtables?”

Let’s look at ThreadLocalMap’s getEntry method, which, without thinking, must fetch data from a hash table. It looks like this:

private Entry getEntry(ThreadLocal<? > key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; // If (e! = null && e.get() == key) return e; Return getEntryAfterMiss(key, I, e); return getEntryAfterMiss(key, I, e); }Copy the code

Since we’re evaluating from a hash table, there must be two cases in all of this method, hash conflicts and no hash conflicts. First, if there are no hash conflicts, simply fetch the ith element from the Entry array. If there are hash conflicts, linear probing is required to find the key’s location. GetEntryAfterMiss is an implementation of linear probing, which is nothing more than looping over and comparing. I won’t post the code for this method here.

It’s not that hard to understand the code for ThreadLocalMap if you know hash tables. But there is a question here, why does an Entry in ThreadLocalMap inherit a WeakReference?

ThreadLocal memory leaks

Why does an Entry in a ThreadLocalMap inherit a WeakReference, making ThreadLocal a WeakReference? We know that a weak reference must be reclaimed in the event of a GC. In general, weak references are used to avoid memory leaks. This is no exception, as ThreadLocal uses weak references to avoid memory leaks.

Imagine declaring a ThreadLocal as a strong reference that needs to be reclaimed once the ThreadLocal is no longer in use. But at this point, the Entry array in ThreadLocalMap holds a ThreadLocal. ThreadLocal cannot be collected, causing a memory leak. You can avoid this problem by declaring ThreadLocal as a weak reference.

So, does that mean that by declaring ThreadLocal in an Entry as a weak reference, we can use ThreadLocal without causing a memory leak? That’s not the case.

Let’s look at the following analysis.

As shown in the figure above, there is one such connection in ThreadLocal. Memory leaks can also occur if Thread is running, since strongly referenced values cannot be reclaimed. Therefore, in general, when this ThreadLocal variable is not needed, the remove method is called to avoid memory leaks.

Four,

From a source point of view, it’s not hard to understand how ThreadLocal works, given that you know your hash tables. ThreadLocal’s set method encapsulates itself in the Entry as a key, along with a value. This Entry is then inserted into the ThreadLocalMap of the current thread. This ThreadLocalMap is a hash table structure that internally uses linear probes and hashes to store entries.

Of course, since there may be multiple ThreadLocal cases, the following code is used:

ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
ThreadLocal<Integer> threadLocal2 = new ThreadLocal<>();

threadLocal.set(1);
threadLocal2.set(2);
Copy the code

Therefore, the structure diagram of ThreadLocalMap can be given as follows: