Source code interpretation of ThreadLocal

Interpretation of ThreadLocal

Basic test usage code

public class ThreadLoaclDemo {
    static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    static String test = "hello";
    public static void main(String[] args) {
        new Thread(() ->{
            threadLocal.set("hello threadLocal1");
            test += "1";
            System.out.println(threadLocal.get());
            System.out.println(test);
        }).start();

        new Thread(() ->{
            threadLocal.set("hello threadLocal2");
            test += "2";
            //threadLocal.remove();  Clears the localVariable in the local memory of the current thread
            System.out.println(threadLocal.get());
            System.out.println(test);
        }).start();
    }
}

We usually introduce ThradLocal to consider such scenarios

When multithreading processes shared variables, there will be security problems. In order to ensure security, appropriate synchronization operations are usually carried out

The general measure of synchronization is locking

While locking ensures security, it also has related efficiency and concurrency problems

Is there such a variable that the thread uses its own when it gets it?

Following this idea, it is easy to feel that if we use ThreadLocal, the local variables of the thread are stored in the ThreadLocal instance. Each thread accesses its own variables by going to ThreadLocal to get data through the current thread instance.

Not really

The local variable of the thread is stored in the threadlocals of the current thread (calling thread)

Through the source code, we find that there are two variables in the Thread class

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

It is null by default, but will not be created until the thread first calls the set or get method of ThradLocal class

First analyze the set, get and remove methods

public T get() {
    // Get current thread
    Thread t = Thread.currentThread();
    // Get the threadLocalMap threadLocals of the current user thread
    ThreadLocalMap map = getMap(t);
    // If threadLocals is not empty
    if (map != null) {
        // Get the value in the local variable of the current thread from threadLocals
        // key is the current ThreadLocal instance
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // If threadLocals is null, the threadLocals member variable of the current thread is initialized
    return setInitialValue();
}

private T setInitialValue() {
    // Initialize to null
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
    return value;
}

Through the source code discovery, the first step is to obtain the current thread. The current thread is used to call the getMap method to obtain the current thread's own variable (threadlocal), which is bound to the thread's member variable

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

Look at set, the source code is similar

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // Line 424
        // Store values in threadlocals
        map.set(this, value);
    } else {
        createMap(t, value);
    }
}

The remove method removes the ThreadLocal instance in the threadlocals of the current thread

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null) {
        m.remove(this);
    }
}

Through these three methods, we can know that ThreadLocal actually acts as a tool class

It has not manipulated the elements in this class too much

Instead, use the threadlocals in the current thread in a disguised way

He calls the set method, stores value in the threadlocals of the calling thread, and calls the get method to extract it.

If the calling thread does not terminate all the time, the variables in the existing threadlocks will not be removed, which may lead to memory overflow. Therefore, after application, you need to call the remove method in ThreadLocal to delete the local variables in the threadlocks of the corresponding thread

Why map structure?

Because there is more than one ThreadLocal variable per thread

ThreadLocal also has a short board, that is, ThreadLocal sharing cannot be used in parent-child threads

I think this is not a short board of ThreadLocal, because ThreadLocal operations are based on the current thread

Then InheritableThreadLocal is born by luck

There are only three methods in the source code, that is, the three methods of the rewritten ThreadLocal

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }
    /**
     * Get inheritableThreadLocals
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    /**
     * Override the createMap method to create inheritablethreadlocales instead of threadlocales
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

To understand how InheritableThreadLocal allows child threads to access parent Thread variables, you need to start with the Thread constructor

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

track

/** Initializes the thread with the current AccessControlContext.*/
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize) {
    init(g, target, name, stackSize, null, true);
}

track

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");
    }

    this.name = name;
    // Get current thread
    Thread parent = currentThread();
	/* Partial code omission */
    /*Start entering the parent-child thread section*/
    if (inheritThreadLocals && parent.inheritableThreadLocals != null) {
        // If the inheritableThreadLocals of the parent thread is not empty
        // Then set the inheritableThreadLocals variable in the child thread
        // Using the inheritableThreadLocals of the parent thread as the constructor, create a new ThreadLocalMap variable and assign it to the inheritableThreadLocals of the child thread
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    }
    this.stackSize = stackSize;
    tid = nextThreadID();
}

Trace createInheritedMap

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

Then go to ThreadLocalMap to see how it is constructed through the ThreadLocalMap of the parent thread

/**
* Construct a new map containing all inheritable threadlocales from the given parent map
* Called only by createInheritedMap
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
    // Get entity
    Entry[] parentTable = parentMap.table;
    // Get quantity
    int len = parentTable.length;
    setThreshold(len);
    table = new Entry[len];
    // Copy, repackage
    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) {
                // Here is the childValue method overridden by calling InheritableThreadLocal
                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++;
            }
        }
    }
}

That's it

When the parent thread creates a child thread, the constructor will copy the local variable of inheritableThreadLocals in the parent thread and save it in inheritableThreadLocals in the child thread

Usage scenario of inheritableThreadLocals

The child thread needs to use the user login information stored in the parent thread

The middleware needs to record the whole call link of the unified id trace

The beauty of java Concurrent Programming

Keywords: Java Back-end source code

Added by CGRRay on Wed, 27 Oct 2021 11:21:10 +0300