Comprehensive analysis of thread variable copy ThreadLocal principle of Java Concurrent Programming

introduction

In previous articles: Thoroughly understand the implementation principle of Synchronized keyword in Java Concurrent Programming In, we talked about the "three elements" caused by thread safety for the first time: multithreading, shared resources / critical resources and non atomic operation. In short, if multiple threads operate non atomic on critical resources at the same time, thread safety problems may arise. If we want to solve the thread safety problem, we only need to destroy any of the three elements, as follows:

  • ① Destroy multithreading condition: at the same time, a thread performs non atomic operations on shared resources, which will not cause thread safety problems
  • ② Destroy shared resource conditions: multiple threads perform non atomic operations on local resources at the same time, which will not cause thread safety problems
  • ③ Destroy non atomic conditions: multiple threads perform atomic operations on shared resources at the same time, which will not cause thread safety problems

The statement of "three elements" is only a personal understanding and can be corrected in case of doubt

In the previous article, we mentioned that CAS lock free mechanism, synchronized implicit lock and ReetrantLock explicit lock can solve thread safety problems. Among these schemes, CAS mechanism uses the third point above: destroying non atomic conditions and ensuring atomicity to solve thread safety problems; Synchronized and ReetrantLock use the first point above: breaking the multithreading condition and allowing only one thread to access critical resources at the same time to solve this problem. The ThreadLocal mentioned in this article solves the thread safety problem by destroying the shared resource conditions.

1, Concept and application of ThreadLocal

ThreadLocal thread local copy is also called thread local variable and thread local storage in many places, but in general, it describes ThreadLocal. During execution, ThreadLocal will create a copy of the variable in each thread, which can only be accessed by each thread itself. Let's take a look at the ThreadLocal class and some methods it provides:

// Omit the method body (the source code will be analyzed in detail later)
public class ThreadLocal<T> {
    // Constructor
    public ThreadLocal() {}
    
    // Initialization method: this method can be used to initialize and set values when creating ThreadLocal objects
    protected T initialValue()
    
    // Gets a copy of the variable held by ThreadLocal in the current thread
    public T get() 
    
    // Sets a copy of the variable in the current thread
    public void set(T value)
    
    // Removes a copy of a variable in the current thread
    public void remove()
    
    // Internal subclass: method that extends the initialization value of ThreadLocal and supports Lambda expression assignment
    static final class SuppliedThreadLocal<T> extends ThreadLocal<T>
    
    // Internal class: a customized hashMap, which is only used to maintain the local variable value of the current thread.
    // Only ThreadLocal class has operation permission on it, which is the private property of Thread.
    // In order to avoid a series of problems caused by data with large space or long life cycle residing in memory,
    // The key of hashtable is weak references.
    // When the heap space is insufficient, the unreferenced entries will be cleaned up.
    static class ThreadLocalMap
    
    // Omit other codes
}

These are some of the main methods provided by ThreadLocal. When creating a ThreadLocal object, you can initialize the variable copy with initialValue(), change the value or set the thread variable copy with the set() method, obtain the variable copy with the get() method, and remove() can remove the variable copy in the current thread. Let's take a look at an example:

public class DBUtils {
    private static Connection connection = null;

    public static Connection getConnection() throws SQLException {
        if (connection == null)
            connection = DriverManager.getConnection(
                    "jdbc:mysql:127.0.0.1:3306/test?user=root&password=root");
        return connection;
    }

    public static void closeConnection() throws SQLException {
        if (connection != null)
            connection.close();
    }
}

Suppose there is the above database connection tool class DBUtils. There is no problem that the above code runs in a single threaded environment, but there will be a problem if this tool class is left in a multi-threaded environment. Obviously, in the getConnection() method for obtaining connections, if multiple threads execute if (connection == null) judgment at the same time, it is likely to lead to the creation of multiple connection objects. Because connection is a shared resource, thread safety should also be guaranteed during operation. Otherwise, in the case of multithreading, it may cause: one thread is still executing SQL, and the other thread calls the closeConnection() method to close the connection object.

So in the above example, how can we solve the problems we encounter? Simple~

public class DBUtils {
    private static volatile Connection connection = null;

    public synchronized static Connection getConnection() throws SQLException {
        if (connection == null)
            connection = DriverManager.getConnection(
                    "jdbc:mysql:127.0.0.1:3306/test?user=root&password=root");
        return connection;
    }

    public synchronized static void closeConnection() throws SQLException {
        if (connection != null)
            connection.close();
    }
}

We can ensure thread safety by adding volatile keyword to the shared variable connection and synchronized keyword to the method of operating critical resources. Or we can do the same:

public class DBUtils {
    private static volatile Connection connection = null;
    private static ReentrantLock lock = new ReentrantLock();

    public static Connection getConnection() throws SQLException {
        lock.lock(); //Acquire lock
        if (connection == null)
            connection = DriverManager.getConnection(
                    "jdbc:mysql:127.0.0.1:3306/test?user=root&password=root");
        lock.unlock(); // Release lock
        return connection;
    }

    public static void closeConnection() throws SQLException {
        lock.lock(); //Acquire lock
        if (connection != null)
            connection.close();
        lock.unlock(); // Release lock
    }
}

However, the above two methods can ensure thread safety, but the disadvantages are also obvious:

When a thread is executing SQL, other threads can only get the connection after the current thread completes processing, which will greatly affect the efficiency of the program.

We can think about whether we need to turn the connection object into a shared resource here? The result is obvious. In fact, it is unnecessary, because each thread can hold a connection object for DB operation, and there is no dependency between each thread on the operation of the connection object. Can we do this?

public class DBUtils {
    public static Connection getConnection() throws SQLException {
        return DriverManager.getConnection(
                    "jdbc:mysql:127.0.0.1:3306/test?user=root&password=root");;
    }

    public static void closeConnection(Connection connection) throws SQLException {
        if (connection != null)
            connection.close();
    }
}

Theoretically, it is feasible, because different connection objects are created each time a thread operates a DB, so there is no thread safety problem. However, every time a thread accesses the DB, it needs to create a new connection object and close it again after it is used up. In the execution process, it will frequently obtain / close the database connection, which will not only affect the overall efficiency of the system, but also cause great pressure on the DB server, and even directly lead to system crash in severe cases.

In this case, we can use ThreadLocal to solve such problems, as follows:

public class DBUtils {
    private static ThreadLocal<Connection> connectionHolder =
    new ThreadLocal<Connection>(){
        @SneakyThrows
        public Connection initialValue(){
            return DriverManager.getConnection(
                    "jdbc:mysql:127.0.0.1:3306/test?user=root&password=root");
        }
    };

    public static Connection getConnection() throws SQLException {
        return connectionHolder.get();
    }
}

In the above example, we can use ThreadLocal to create a Connection variable copy for each thread, so as to achieve what we said at the beginning: ThreadLocal solves the thread safety problem by destroying the shared resource conditions. Each thread operates on its own local copy variables, which naturally does not constitute the "three elements".

ThreadLocal usage scenario

  • ① Context passing. An object needs to be passed and used hierarchically in multiple methods, such as user identity, task information, call chain ID, association ID (such as the uniqueID of the log, which is convenient to string multiple logs), etc. if you use the responsibility chain mode to add a context parameter to each method, it will be more troublesome. At this time, you can use ThreadLocal to set the parameters. You need to get it when you use it.
  • ② Data isolation between threads. If the spring transaction management mechanism is implemented, ThreadLocal is used to ensure that the database operation in a single thread uses the same database Connection. At the same time, in this way, the business layer does not need to perceive and manage the Connection object when using transactions. Through the propagation level, it can skillfully manage the switching, suspension and recovery between multiple transaction configurations.
  • ③ In general, ThreadLocal is rarely used in the project development process, and its more applications are in the framework source code, such as the TransactionSynchronizationManager class in the transaction isolation mechanism of the Spring framework, as well as the secondary encapsulation class FastThreadLocal in the Netty framework.
  • ④ Previous ThreadLocal application case:
// Date tool class
private static ThreadLocal<DateFormat> threadLocal = 
        ThreadLocal.withInitial(()-> 
            new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));

public static Date parse(String dateStr) {
    Date date = null;
    try {
        date = threadLocal.get().parse(dateStr);
    } catch (ParseException e) {
        e.printStackTrace();
    }
    return date;
}

2, Principle analysis of ThreadLocal

We explained ThreadLocal briefly earlier. As a storage type class, ThreadLocal focuses on reading and writing get() and set(). Now we can go deep into the source code to explore the mystery of ThreadLocal.

2.1 principle analysis of creating variable copy with ThreadLocal

Start with ThreadLocal.set() method:

// ThreadLocal class → set() method
public void set(T value) {
    // Gets the current execution thread
    Thread t = Thread.currentThread();
    // Gets the threadlocals member variable of the current thread
    ThreadLocalMap map = getMap(t);
    // If the map is not empty, add value to the map
    if (map != null)
        map.set(this, value);
    // If the map is empty, create a map for the current thread first, and then add value to the map
    else
        createMap(t, value);
}

ThreadLocal.set() method is generally divided into three steps:

  • Call getMap() to get the ThreadLocalMap of the current thread
  • If the map is not empty, the value passed in is added to the map
  • If the map is empty, create a map for the current thread first, and then add value to the map

First, let's take a look at the getMap(Thread) method:

// ThreadLocal class → getMap() method
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

Is there something unexpected? In the getMap(Thread) method, the member variable threadLocals of the current thread object is called and returned:

Thread class: ThreadLocal.ThreadLocalMap threadLocals = null;

It can be seen that the member variable threadLocals of Thread class is actually ThreadLocalMap, and ThreadLocalMap is a customized version of HashMap for ThreadLocal and an internal class of ThreadLocal, as follows:

// ThreadLocal class
public class ThreadLocal<T> {
    // ThreadLocal internal class: ThreadLocalMap
    static class ThreadLocalMap {
        // ThreadLocalMap internal class: Entry
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    }
}

There is also an internal class Entry in the ThreadLocalMap class, which inherits from the weak reference type of WeakReference. The structure is as follows:

Let's look at the createMap() method:

// ThreadLocal class → createMap() method
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

By now, we should have understood how ThreadLocal creates a variable copy for each thread:

Within each Thread, there is a ThreadLocal.ThreadLocalMap type member variable threadLocals. This threadLocals is used by each Thread to store variable copies. The key value is the current ThreadLocal object and the value is the variable copy (i.e. variable of type T). The first threadlocales of each Thread object are empty. When the Thread calls ThreadLocal.set() or ThreadLocal.get() methods (which will be analyzed later), it will call createMap() method to initialize threadlocales. Then, if you want to use copy variables in the current Thread, you can find them in threadLocals through the get method.

The principle is as follows:

2.2. Principle analysis of obtaining variable copy by ThreadLocal

In the above process, we have analyzed the principle of ThreadLocal creating variable copies. Next, let's look at ThreadLocal.get() method:

// ThreadLocal class → get() method
public T get() {
    // Gets the current execution thread
    Thread t = Thread.currentThread();
    // Gets the ThreadLocalMap of the current thread
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // If the map is not empty, take the current ThreadLocal object as the key to obtain the corresponding value
        ThreadLocalMap.Entry e = map.getEntry(this);
        // If the obtained value is not empty, the obtained value is returned
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // If the map is empty, the setInitialValue method is called
    return setInitialValue();
}

In ThreadLocal.get() method, there are three steps:

  • Get the current execution thread, and get the map of ThreadLocalMap type through getMap(Thread) method
  • Take the current ThreadLocal object this as the key, and try to obtain the < key, value > key value pair in the map. If the acquisition is successful, value will be returned
  • If the map obtained in the first step is empty, the setInitialValue() method is called to return value

After calling the get() method, the threadLocals member variable (i.e. ThreadLocalMap) of the current thread will be obtained first. If the map is not empty, this will be taken as the key to obtain the copy of the variable stored in ThreadLocal. If it is empty, the setInitialValue() method will be called:

// ThreadLocal class → setInitialValue() method
private T setInitialValue() {
    // Get ThreadLocal initialization value
    T value = initialValue();
    Thread t = Thread.currentThread();
    // Gets the map of the current thread
    ThreadLocalMap map = getMap(t);
    // If the map is not empty, the initialization value is added to the map container
    if (map != null)
        map.set(this, value);
    // If the map is empty, a ThreadLocalMap container is created
    else
        createMap(t, value);
    return value;
}

// ThreadLocal class → initialValue()() method
protected T initialValue() {
    return null;
}

setInitialValue() is somewhat similar to the ThreadLocal.set(value) method analyzed earlier. In the setInitialValue() method, the initialValue() method will be called first to obtain the initialization value, and the initialValue() method returns null by default, but the initialValue() method can be overridden when creating ThreadLocal objects, as follows:

private static ThreadLocal<Object> threadlocal =
new ThreadLocal<Object>(){
    @SneakyThrows
    public Object initialValue(){
        return new Object();
    }
};

After obtaining the initialized value, get the threadlocales of the current thread again. If it is not empty, add this as the key and the initial value as value into the map. If threadLocals of the current thread is null, createMap(t, value) will be called first; Create a ThreadLocalMap for the current thread, add this and the initial value to the map in the form of k-v, and then return the value. If the ThreadLocal object is not created and there is no initialization value, null will be returned. So far, the whole ThreadLocal.get() method ends. As follows:

3, InheritableThreadLocal details

Through the above analysis, it is not difficult to know that the purpose of ThreadLocal design is to open up its own local variable storage area for each thread (it is not designed to solve thread safety problems, but using ThreadLocal can avoid certain thread safety problems). Therefore, if you want to share the data in ThreadLocal to child threads, It will be extra difficult to achieve. InheritableThreadLocal came into being. InheritableThreadLocal enables multiple threads to access the value of ThreadLocal, ok~. Last example:

private static InheritableThreadLocal<String> itl = 
new InheritableThreadLocal<String>();
public static void main(String[] args) throws InterruptedException {
    System.out.println(Thread.currentThread().getName()
    + "......Thread execution......");
    itl.set("Bamboo....");
    System.out.println("Parent thread: main Thread assignment: Bamboo....");
    new Thread(()->{
        System.out.println(Thread.currentThread().getName()
        + "......Thread execution......");
        System.out.println("Child thread: T1 Thread read value:"+itl.get());
    },"T1").start();
    System.out.println("end of execution.....");
}

As shown in the above code, create an InheritableThreadLocal type variable itl, assign values in the parent thread main, and then start a child thread T1 to read values. The execution results are as follows:

/*
 Execution results:
    main......Thread execution
    Parent thread: main thread assignment: Bamboo
    End of execution
    T1......Thread execution
    Sub thread: T1 thread read value: Bamboo
*/

It is not difficult to see from the results that the value read by the child thread T1 is actually the value set by the parent thread of main. Why? Let's take a look at the source code of InheritableThreadLocal:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    // Used when the parent thread copies the InheritableThreadLocal variable to the child thread
    protected T childValue(T parentValue) {
        return parentValue;
    }
    // Returns the inheritableThreadLocals member variable of the thread
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    // Initialize the member variable inheritableThreadLocals of the thread
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

The getMap() and createMap() methods of the parent ThreadLocal class are overridden in InheritableThreadLocal. When we analyzed ThreadLocal earlier, we mentioned that there is a member variable threadlocals in the Thread class Thread. In fact, in addition to the threadlocals member, there is another member variable inheritableThreadLocals in the Thread, as follows:

ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

Therefore, when the InheritableThreadLocal variable is operated, only the inheritableThreadLocals member of the thread is affected, not the 'threadlocals' member.

3.1 value transfer principle of InheritableThreadLocal parent-child thread

After understanding the composition of InheritableThreadLocal, let's then analyze how the parent-child thread passes values. When creating a child thread, we usually directly select new Thread():

Thread t1 = new Thread();

Next, the constructor of Thread class will be called to create Thread object:

// Thread class → constructor
public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

// Thread class → init() method overload
private void init(ThreadGroup g, Runnable target, String name,
                long stackSize) {
    // Call the init method of all parameters to complete thread initialization
    init(g, target, name, stackSize, null, true);
}

// Thread class → init() method
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;
    // Gets the current execution thread as the parent thread
    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();
    if (g == null) {
        // Confirm whether the created thread is a child thread
        // If SecurityManager is not empty, get the thread group of SecurityManager
        if (security != null) {
            g = security.getThreadGroup();
        }

        // If the thread grouping is not set for the created thread in the SecurityManager,
        // The parent thread group of the currently executing thread parent is used
        if (g == null) {
            g = parent.getThreadGroup();
        }
    }

    // Whether threadgroup is explicitly passed in or not, access is checked
    g.checkAccess();

    // If the SecurityManager is not empty, check whether the permissions are valid
    // Is SUBCLASS_IMPLEMENTATION_PERMISSION
    if (security != null) {
        if (isCCLOverridden(getClass())) {
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }
    g.addUnstarted();
    // Set the current execution thread as the parent thread of the created thread
    this.group = g;
    this.daemon = parent.isDaemon();
    this.priority = parent.getPriority();
    // Get thread context class loader
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    // Sets the thread context class loader for the currently created thread
    this.inheritedAccessControlContext =
            acc != null ? acc : AccessController.getContext();
    this.target = target;
    setPriority(priority);
    // a key!!! It will be analyzed in detail later
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    // Assign the default thread stack size to the created thread
    this.stackSize = stackSize;
    // Set thread ID
    tid = nextThreadID();
}

The above is the initialization process when creating a thread. There is such a code in the init() method:

if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);

When a child Thread is created in the default way, the way a Thread executes the new instruction to create a Thread object is called the default way, and this way will set the Thread currently executing the creation logic as the parent Thread of the created Thread. If the inheritableThreadLocals member variable of the parent Thread is not empty, this.inheritableThreadLocals=ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);, Pass the parent Thread inheritableThreadLocals to the child Thread. Next, you can look at the ThreadLocal.createInheritedMap() method:

// ThreadLocal class - > createinheritedmap() method
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}

//  ThreadLocalMap class - > private constructor
// Build a ThreadLocalMap that contains Inheritable ThreadLocals in all parentmaps
// This function is only called by createInheritedMap()
private ThreadLocalMap(ThreadLocalMap parentMap) {
    // Gets all entries of the parent thread
    Entry[] parentTable = parentMap.table;
    // Gets the number of entries for the parent thread
    int len = parentTable.length;
    setThreshold(len);
    // ThreadLocalMap uses the Entry[] table to store ThreadLocal
    table = new Entry[len];

    // Copy the entries of the map in the parent thread one by one
    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) {
                // Why not use the childValue method instead of direct assignment?
                // Because childValue directly returns e.value internally,
                // The purpose of this implementation may be to ensure the maximum scalability of the code
                // Because childValue() can be overridden
                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++;
            }
        }
    }
}

After calling the ThreadLocal.createInheritedMap() method, all entries of the inheritableThreadLocals member in the parent thread will be copied to the inheritableThreadLocals member of the child thread. So far, the whole creation process is completed. From this process, we can know that the value passing of parent-child threads is realized by copying all entries of inheritableThreadLocals when creating threads.

4, Principle analysis of ThreadLocalMap

The principle of ThreadLocal involves three core classes: ThreadLocal, Thread and ThreadLocalMap. There are two member variables in Thread class: threadLocals and inheritableThreadLocals. The types of these two member variables are ThreadLocalMap. After a series of analysis, we can know that these two member variables are the final container for storing Thread variable copies. As mentioned earlier, ThreadLocalMap is a customized version of HashMap in ThreadLocal, However, it does not implement the Map interface, but implements it internally through the array type storage entry. Entry simply inherits the WeakReference soft reference and does not realize the subsequent node pointing similar to Node.next in HashMap, so ThreadLocalMap is not an implementation in the form of linked list. How does ThreadLocalMap resolve hash conflicts without a linked list structure? From the perspective of source code:

// ThreadLocalMap class → Entry static internal class
static class Entry extends WeakReference<ThreadLocal<?>> {
    // value: a copy of the stored variable
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
           super(k);
            value = v;
    }
}

// ThreadLocalMap class → construction method
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    // Member variable table (array structure), initial_ Constant with capability value of 16
    table = new Entry[INITIAL_CAPACITY];
    // Bit operation, similar to modular algorithm, calculates the location to be stored
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

It is not difficult to know from the above code that when calling the createMap() method to create the ThreadLocalMap example, in the ThreadLocalMap construction method, an Entry array with a length of 16 will be initialized for the member variable table, and a subscript index value i will be determined through hashCode and length bit operation, which is the subscript position stored in the table array. Now let's take a simple example to understand:

ThreadLocal<Zero> tl0 = new ThreadLocal<Zero>();
ThreadLocal<One> tl1 = new ThreadLocal<One>();
ThreadLocal<Two> tl2 = new ThreadLocal<Two>();

new Thread(()->{
    tl0.set(new Zero());
    tl1.set(new One());
    tl2.set(new Two());
},"T1").start();

new Thread(()->{
    tl0.set(new Zero());
    tl1.set(new One());
    tl2.set(new Two());
},"T2").start();

In the case, three ThreadLocal objects are created: tl0, tl1, tl2 and Two Thread objects: T1 and T2. After the previous analysis, we know that a ThreadLocalMap type member variable threadlocals is maintained in each Thread object to store the copy variables of each Thread. Therefore, both T1 and T2 maintain a ThreadLocalMap. When T1 and T2 operate tl0, tl1 and tl2, Zero, One and Two will be stored in different positions of the array in the form of key value. This array is the member Entry[] table in the ThreadLocalMap class mentioned above. How to determine the storage location of tl0 Zero, tl1 One and tl2 Two k-vs in the table? As follows:

  //ThreadLocalMap class → set() method
  private void set(ThreadLocal<?> key, Object value) {
    // Get table and its length
    Entry[] tab = table;
    int len = tab.length;
    // Use the hash value of key and array length calculation to obtain the index value
    int i = key.threadLocalHashCode & (len-1);

    // Traverse the table, update the value if it already exists, and create if it does not exist
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        // If the key s are the same, replace the old value with the new value
        if (k == key) {
            e.value = value;
            return;
        }
        // If table[i] is empty, a new Entry store is created
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    
    // If the table[i] is not null and the key s are different,
    // If the null position is not found after traversing the array,
    // It means that the array needs to be expanded, and the array needs to be expanded twice
    tab[i] = new Entry(key, value);
    int sz = ++size;
    // After cleaning up the expired data, the available data in the array still accounts for
    // In the case of 3 / 4, double the direct expansion
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

We can easily find from the source code that after the set() method starts, we will first obtain the length of the table and the hash value of the ThreadLocal object to calculate a subscript index value I: int i = key. Threadlocalhashcode & (len-1);.

// Related codes of threadLocalHashCode in ThreadLocal
private final int threadLocalHashCode = nextHashCode();

private static AtomicInteger nextHashCode =
    new AtomicInteger();

// 0x61c88647 is the Fibonacci hash multiplier, and the hash result will be more scattered
private static final int HASH_INCREMENT = 0x61c88647;

private static int nextHashCode() {
    // Atomic counter self increment
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

Because the members related to hash codes in ThreadLocal are decorated with static keywords, each time a ThreadLocal object is created, the auto increment method will be called during object initialization to generate a hash value for the ThreadLocal object:

private final int threadLocalHashCode = nextHashCode();

And HASH_INCREMENT=0x61c88647 is because 0x61c88647 is the Fibonacci hash multiplier. The hash results will be evenly distributed, which can avoid hash conflicts to a great extent.
After the above analysis, we can draw a conclusion: the threadlocals of each thread will maintain an independent table array internally, and each ThreadLocal object has the same position in different thread tables. For the same thread, different copies of ThreadLocal variables will be encapsulated into Entry objects and stored in their own internal table.

ok ~, go on, after int i = key.threadlocalhashcode & (len-1); After calculating the index subscript value, it will start to traverse the table, and then start to judge. If the table[i] position is not empty, but the original key value is the same as the new key value, replace the previous old value with the new value, refresh the value value and return:

if (k == key) {
    e.value = value;
    return;
}

If the table[i] position is empty, create an Entry object to encapsulate the K-V value and place the object in the table[i] position:

if (k == null) {
    replaceStaleEntry(key, value, i);
    return;
}

If the table[i] location is not empty and the keys are not the same, call nextIndex(i,len) to obtain the next location information and judge whether the next location is empty until the empty location is found:

e = tab[i = nextIndex(i, len)] // Loop body at the end of a for loop

When the table[i] position is not empty and the keys are different, if you traverse the entire table array and do not find an empty subscript position, it means that the array is full and needs to be expanded, call rehash() to double the capacity of the array:

// Meet the conditions and double the capacity of the table array
if (!cleanSomeSlots(i, sz) && sz >= threshold)
    rehash();

At this point, the entire ThreadLocalMap stored procedure ends, as follows:

Next, let's look at the get principle of ThreadLocalMap:

// ThreadLocal class - > threadlocalmap internal class - > getentry() method
private Entry getEntry(ThreadLocal<?> key) {
    // Through the hash value of the 'ThreadLocal' object and the length of the 'table' array
    // Calculate get index value ` i`
    int i = key.threadLocalHashCode & (table.length - 1);
    // Get the element at table[i]. If it is not empty and the key is the same, return
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    // If the keys are different, traverse the elements after the entire table[i] to obtain the value of the corresponding key
    else
        return getEntryAfterMiss(key, i, e);
}

// ThreadLocal class - > threadlocalmap internal class - > getentryaftermiss() method
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
    // Traverse the elements after the entire table[i]
    while (e != null) {
        ThreadLocal<?> k = e.get();
        // If the key s are the same, the corresponding elements are returned
        if (k == key)
            return e;
        if (k == null)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}

In the same way as set in the previous analysis, during get, the index value I will also be calculated according to the hash value of ThreadLocal object and the length of table array, and then judge whether the key value of Entry object in this location is the same as that of get(key). If it is the same, the value of this location will be obtained directly and returned. If not, traverse all elements after table[i] in the whole array, and loop to determine whether the key at the next position is consistent with the key passed in. If so, get the return.

5, ThreadLocal considerations

5.1 ThreadLocal thread safety

Although ThreadLocal can solve the thread safety problem to a certain extent, the original intention of ThreadLocal design is to open up its own storage space for each thread. Therefore, if the object of ThreadLocal.set() is shared, thread safety problems will also occur in the case of multithreading.

5.2. Generation of ThreadLocal replica variable

The ThreadLocal variable does not copy and clone an object for each thread, but creates a new object for each thread.

5.3. ThreadLocal may generate dirty data in the process pool

Because the thread pool will reuse threads, and after the previous task executed by the thread performs the set() operation on ThreadLocal, it does not call remove() to remove the variable copy after the end of thread run(). If the next Runnable task directly performs the get() operation on ThreadLocal, it may read dirty data.

5.4 ThreadLocal may cause memory leakage

When storing the variable copy in ThreadLocalMap, the Entry object uses the weak reference of ThreadLocal as the key. If a ThreadLocal object has no external strong reference to point to it, the GC mechanism will recycle the keys of these weak reference types when the heap memory is insufficient, which will cause ThreadLocalMap < null, Object > and the thread will not end (such as the resident thread in the thread pool), Then there will always be a strong reference chain for these value values with key=null: Thread.threadlocals(Reference) member variable - > ThreadLocalMap object - > Entry object - > object value object, which makes GC unable to recycle and causes memory leakage. This object is the leaked object. As for the reason why the key should be set to weak reference type:

If the key is not designed as a weak reference type, it will cause a memory leak of value in the entry

Solution

For the two problems in 5.3 and 5.4, we can manually call the ThreadLocal.remove() method to clear the ThreadLocal variable copy after using ThreadLocal.

Tags: Java Multithreading

Posted on Fri, 22 Oct 2021 18:40:49 -0400 by davidppppppppppp