Super parsing: the use and principle of ThreadLocal are the key points

Time:2021-12-5

Like and watch again to form a habit!

In the methods of dealing with multithread concurrency security, the most commonly used method is to use locks to control the access of multiple different threads to the critical area.

However, no matter what kind of lock, optimistic lock or pessimistic lock, it will have a certain impact on performance in case of concurrency conflict.

Is there a way to completely avoid competition?

The answer is yes. This is ThreadLocal.

Literally, ThreadLocal can be interpreted as a local variable of a thread, that is, a ThreadLocal variable can only be accessed by its own thread, and can not be accessed by other threads, so thread competition is naturally avoided.

Therefore, ThreadLocal provides a different thread safety method. It does not try to solve the thread conflict when it occurs, but completely avoids the conflict.

Basic use of ThreadLocal

Create a ThreadLocal object:

private ThreadLocal<Integer> localInt = new ThreadLocal<>();

The above code creates a localint variable. Since ThreadLocal is a generic class, it specifies that the type of localint is an integer.

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

public int setAndGet(){
    localInt.set(8);
    return localInt.get();
}

The above code sets the value of the variable to 8, and then obtains this value.

Because of the value set in ThreadLocal, only the current thread can see it, which means that you can’t initialize the value for it through other threads. To make up for this, ThreadLocal provides a withinitial() method to uniformly initialize the ThreadLocal values of all threads:

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

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

Implementation principle of ThreadLocal

ThreadLocal variable is only visible in a single thread. How does it do that? Let’s start with the most basic get () method:

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

You can see that the so-called ThreadLocal variable is stored in the map of each thread. This map is the threadlocals field in the thread object. As follows:

ThreadLocal.ThreadLocalMap threadLocals = null;

Threadlocal.threadlocalmap is a special map. The key of each entry is a weak reference:

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

The advantage of this design is that if the variable is no longer used by other objects, the ThreadLocal object can be recycled automatically to avoid possible memory leakage (note that the value in the entry is still a strong reference. See the decomposition below for how to recycle).

Understand the memory leak problem in ThreadLocal

Although the key in threadlocalmap is a weak reference and will be automatically recycled when there is no external strong reference, the value in the entry is still a strong reference. The reference chain of this value is as follows:

Super parsing: the use and principle of ThreadLocal are the key points

It can be seen that only when the thread is recycled can this value be recycled. Otherwise, as long as the thread does not exit, there will always be a strong reference to value. However, it is an extremely demanding requirement that each thread exits. For the thread pool, most threads will always exist in the whole life cycle of the system. In that case, the value object may be leaked. The processing method is to clean up when threadlocalmap performs set(), get(), and 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 it cannot be found, it will try to clean up. If you always access the existing key, the cleaning will never come in
        return getEntryAfterMiss(key, i, e);
}

The following is the implementation of getentryaftermiss():

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

    while (e != null) {
        //The whole e is an entry, that is, a weak reference
        ThreadLocal<?> k = e.get();
        //If found, return
        if (k == key)
            return e;
        if (k == null)
            //If the key is null, the weak reference has been recycled
            //Then you have to recycle the value here
            expungeStaleEntry(i);
        else
            //If the key is not the one you are looking for, it indicates that there is a hash conflict. Here is to deal with the conflict and find the next entry
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}

The method expungestaleentry() is really used to reclaim value. In the remove() and set() methods, this method will be called directly or indirectly to clean up value:

It can be seen from here that ThreadLocal has made great efforts to avoid memory leakage. It not only uses weak references to maintain keys, but also checks whether the key is recycled on each operation, and then recycles value.

However, it can also be seen that ThreadLocal does not guarantee 100% memory leakage.

For example, unfortunately, your get () method always accesses several existing ThreadLocal, so the cleanup action will not be executed. If you don’t have a chance to call set () and remove (), this memory leak will still occur.

Therefore, a good habit is still: when you don’t need this ThreadLocal variable, actively call remove (), which is good for the whole system.

Hash conflict handling in threadlocalmap

The implementation of threadlocalmap as a HashMap is different from that of java.util.hashmap. For java.util.hashmap, the linked list method is used to handle conflicts:

Super parsing: the use and principle of ThreadLocal are the key points

However, for threadlocalmap, it uses a simple linear detection method. If there is an element conflict, the next slot is used for storage:

Super parsing: the use and principle of ThreadLocal are the key points

Specifically, the whole process of set() is as follows:

Super parsing: the use and principle of ThreadLocal are the key points

Inheritable ThreadLocal — inheritablethreadlocal

In the actual development process, we may encounter such a scenario. The main thread has a child thread, but we hope that the ThreadLocal object in the main thread can be accessed in the child thread, that is, some data needs to be transferred 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. I hope I can get it in the sub thread
        threadLocal.set(i);
        //Here comes a sub thread. We want to access the ThreadLocal above
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
        }).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
}

Execute the above code and you will see:

Thread-0:null
Thread-1:null
Thread-2:null
Thread-3:null

Because there is no ThreadLocal in the child thread. If we want the child thread to see the ThreadLocal of the parent thread, we can use inheritablethreadlocal. As the name suggests, this is a ThreadLocal that supports parent-child inheritance between threads. Use inheritablethreadlocal for ThreadLocal in the above code:

InheritableThreadLocal threadLocal = new InheritableThreadLocal();

After execution, you can see:

Thread-0:0
Thread-1:1
Thread-2:2
Thread-3:3
Thread-4:4

As you can see, each thread can access a data passed from the parent process. Although inheritablethreadlocal looks convenient, you should still pay attention to the following points:

The variable transfer occurs when the thread is created. If the thread in the thread pool is used instead of creating a new thread, it will not work
The assignment of variables is to copy the map of the main thread to the child thread. Their values are the same object. If the object itself is not thread safe, there will be a thread safety problem

Last words

Today, we introduced ThreadLocal, which plays a very important role in Java multithreading development.

Here, we introduce the basic use and implementation principle of ThreadLocal, especially the possible memory leakage based on the current implementation principle.

Finally, it also introduces a special ThreadLocal implementation for transmitting data between parent and child threads, hoping to be helpful to you.

If you want to get more information about ThreadLocal and thoroughly understand ThreadLocal, you can confide in my keywords[information], learn more about Java

~Contact information~
wechat: Mlzg5201314zz
Buckle: 2967728282///

Recommended Today

The real problem of Alibaba IOS algorithm can’t hang up this time

More and more IOS developers continue to enter the peak of job hopping in 2020 Three main trends of interview in 2020: IOS bottom layer, algorithm, data structure and audio and video development Occupied the main battlefield. Data structure and algorithm interview, especially figure, has become the main reason for the failure of most first-line […]