The most common way to deal with multi-threaded concurrency safety is to use locks to control the access of multiple threads to a critical region.

However, both optimistic and pessimistic locks can have an impact on performance when concurrency conflicts occur.

Is there a way to avoid competition altogether?

The answer is yes, this is ThreadLocal.

A ThreadLocal can literally be defined as a thread-local variable, meaning that a ThreadLocal variable is accessible only by its own thread, thus preventing thread contention.

Thus, ThreadLocal offers a different approach to thread-safety, in that rather than resolving conflicts when they occur, it avoids them altogether.

Basic use of ThreadLocal

Create a ThreadLocal object:

private ThreadLocal<Integer> localInt = new ThreadLocal<>();
Copy the code

The above code creates a localInt variable, and since ThreadLocal is a generic class, it specifies that localInt is of type integer.

The following shows how to set and get the value of this variable:

public int setAndGet(a){
    localInt.set(8);
    return localInt.get();
}
Copy the code

The code above sets the value of the variable to 8 and then retrieves it.

Since the value set in ThreadLocal is visible only to the current thread, it means that you cannot initialize it from another thread. To compensate for this, ThreadLocal provides a withInitial() method that uniformly initializes the value of ThreadLocal for all threads:

private ThreadLocal<Integer> localInt = ThreadLocal.withInitial(() -> 6);
Copy the code

The above code sets the initial value of ThreadLocal to 6, which is visible to all threads.

How ThreadLocal works

How is a ThreadLocal variable visible only within a single thread? Let’s start with the basic get() method:

public T get(a) {
    // Get the current thread
    Thread t = Thread.currentThread();
    // Each thread has its own ThreadLocalMap,
    ThreadLocalMap contains all ThreadLocal variables
    ThreadLocalMap map = getMap(t);
    if(map ! =null) {
        // The key of the ThreadLocalMap is the current instance of the ThreadLocal object.
        // Multiple ThreadLocal variables are placed in this map
        ThreadLocalMap.Entry e = map.getEntry(this);
        if(e ! =null) {
            @SuppressWarnings("unchecked")
            // The value fetched from the map is the ThreadLocal variable we need
            T result = (T)e.value;
            returnresult; }}// If the map is not initialized, initialize it here
    return setInitialValue();
}
Copy the code

As you can see, the so-called ThreadLocal variables are stored in each thread’s map. This map is the threadLocals field in the Thread object. As follows:

ThreadLocal.ThreadLocalMap threadLocals = null;
Copy the code

ThreadLocal ThreadLocalMap is a special Map, its key of each Entry is a weak references:

static class Entry extends WeakReference<ThreadLocal<? >>{
    /** The value associated with this ThreadLocal. */
    Object value;
    //key is a weak referenceEntry(ThreadLocal<? > k, Object v) {super(k); value = v; }}Copy the code

The advantage of this design is that if the variable is no longer being used by other objects, the ThreadLocal object can be automatically reclaimed, avoiding possible memory leaks. (Note that the value in Entry is still a strong reference, which is broken down below.)

Understand memory leaks in ThreadLocal

While the key in ThreadLocalMap is a weak reference and is automatically reclaimed when no external strong reference exists, the value in Entry is still a strong reference. The reference chain for this value is as follows:

As you can see, the value has a chance to be reclaimed only if the Thread is reclaimed. Otherwise, as long as the Thread does not exit, there will always be a strong reference to the value. However, requiring each Thread to exit is an extremely demanding requirement. For the Thread pool, most threads will persist throughout the life cycle of the system, which will cause the possibility of leaking value objects. Set (),get(),remove();

Take getEntry() as an example:

private Entry getEntry(ThreadLocal
        key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if(e ! =null && e.get() == key)
        // If the key is found, return directly
        return e;
    else
        // If you can't find it, you try to clean it up. If you always access the existing key, the clean up will never come in
        return getEntryAfterMiss(key, i, e);
}
Copy the code

Here is an implementation of getEntryAfterMiss() :

private Entry getEntryAfterMiss(ThreadLocal<? > key,int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;

    while(e ! =null) {
        // The entire e is an entry, i.e. a weak referenceThreadLocal<? > k = e.get();// If found, return
        if (k == key)
            return e;
        if (k == null)
            // If the key is null, the weak reference has been reclaimed
            // Return the value in the file
            expungeStaleEntry(i);
        else
            // If the key is not the one you are looking for, there is a hash conflict
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}
Copy the code

The expungeStaleEntry() method is called directly or indirectly in remove() and set() to retrieve a value:

As you can see, ThreadLocal is also careful to avoid memory leaks. Not only is the key maintained using weak references, but the key is checked for recycling on each operation, and then the value is reclaimed.

But as you can see, ThreadLocal is not a 100% guarantee against memory leaks.

For example, unfortunately, your get() method is always accessing a fixed number of ever-present ThreadLocal, so the cleanup action won’t be performed, and if you don’t get a chance to call set() and remove(), the memory leak will still occur.

Therefore, it is still a good practice to call remove() when you do not need the ThreadLocal variable, for the benefit of the system as a whole.

Hash conflict handling in ThreadLocalMap

ThreadLocalMap as a HashMap is implemented differently from java.util.hashMap. For java.util.HashMap, the linked list method is used to handle collisions:

For ThreadLocalMap, however, it uses simple linear probing, and if an element conflict occurs, the next slot is used:

Specifically, the entire set() process is as follows:

InheritableThreadLocal — InheritableThreadLocal

In actual development, we might encounter a scenario like this. The main thread opens a child thread, but we want the child thread to be able to access the ThreadLocal object in the main thread, meaning that some data needs to be passed between parent and child threads. Like this:

public static void main(String[] args) {
    ThreadLocal threadLocal = new ThreadLocal();
    IntStream.range(0.10).forEach(i -> {
        // The serial number of each thread is expected to be retrieved from the child thread
        threadLocal.set(i);
        // There is a child thread that we want to access
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
        }).start();
        try {
            Thread.sleep(1000);
        } catch(InterruptedException e) { e.printStackTrace(); }}); }Copy the code

Execute the above code and you will see:

Thread-0:null
Thread-1:null
Thread-2:null
Thread-3:null
Copy the code

Because in child threads, there are no threadLocal. If we want the child to see the parent thread’s ThreadLocal, we can use InheritableThreadLocal. [InheritableThreadLocal] InheritableThreadLocal [InheritableThreadLocal] [InheritableThreadLocal]

InheritableThreadLocal threadLocal = new InheritableThreadLocal();
Copy the code

Execute again and you can see:

Thread-0:0
Thread-1:1
Thread-2:2
Thread-3:3
Thread-4:4
Copy the code

As you can see, each thread has access to one piece of data passed from the parent process. While InheritableThreadLocal looks convenient, there are a few things to be aware of:

  1. The passing of variables occurs when a thread is created. If a thread is used instead of a new thread, it will not work
  2. The assignment of a variable is copied from the map of the main thread to the child thread, and their value is the same object. If the object itself is not thread-safe, then there is a thread-safe problem

Write the last words

Today, we introduced ThreadLocal, which plays an important role in multi-threaded development in Java.

Here, we introduce the basic usage and implementation principles of ThreadLocal, with particular emphasis on memory leaks that may exist based on the current implementation principles.

Finally, I introduced a special ThreadLocal implementation for passing data between parent and child threads.