1 introduction

This class provides thread-local variables. These variables are different from normal variables because each thread that accesses a variable (through its GET or set methods) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that expect to associate state with a thread (for example, a user ID or transaction ID).

For example, the following class generates unique identifiers that are local to each thread. The ThreadId is assigned on the first call to threadid.get () and remains the same in subsequent calls.

Picture captions

2 Continuation system

  • Inheritance? There is no such thing as a utility class in the java.lang package

    Picture captions

  • However, ThreadLocal definitions come with generics, meaning that data can be stored in any format.

    Picture captions

Three attributes

  • ThreadLocal relies on threads attached to each Thread (thread.threadlocals and InheritableThreadLocals) to probe the hash table linearly. The ThreadLocal object acts as a key and searches through threadLocalHashCode. This is a custom hash code (useful only in ThreadLocalMaps) that eliminates collisions in the common case of continuous-constructed ThreadLocal for the same thread, while still performing well in the less common case. ThreadLocal calculates the index of the current ThreadLocal in the ThreadLocalMap using such a hashCode

    Picture captions
    Picture captions

  • The difference between continuously generated hash codes. For setting this value, see article ThreadLocal’s Hash algorithm (0x61C88647).

    Picture captions

  • Note the static modifier,ThreadLocalMap is set to multiple ThreadLocal, which is distinguished by threadLocalHashCode

    Picture captions

4 ThreadLocalMap

ThreadLocalMap is a custom hash table that is only suitable for maintaining thread-local values. No operations are exported outside the ThreadLocal class. This class is package-private, allowing field declarations in Thread classes. To help handle very long lifetimes, hash table nodes use WeakReferences as keys. However, because reference queues are not used, the removal of obsolete nodes is guaranteed only when the table space is insufficient.

static class ThreadLocalMap {

        /** * The node in this hash table uses its primary reference field as a key (always a ThreadLocal object) * inherits WeakReference. * Note that the empty key (entry.get () == null) means that the key is no longer referenced, so the node can be removed from the table. * In the code below, such nodes are called "stale entries" */
        static class Entry extends WeakReference<ThreadLocal<? >>{
            /** The value associated with this ThreadLocal */Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}/** * Initial capacity - must be a power of 2 */
        private static final int INITIAL_CAPACITY = 16;

        Table. Length must be a power of 2 */
        private Entry[] table;

        /** * Number of nodes in the table */
        private int size = 0;

        /** * Next capacity expansion threshold */
        private int threshold; // Default is 0
Copy the code

The characteristics of

  • Key is a reference to ThreadLocal
  • Value is the value stored by ThreadLocal
  • An array of data structures

5 set

5.1 ThreadLocal# set

Sets the current thread copy of this thread-local variable to the specified value. Most subclasses will not need to override this method and will rely only on the initialValue method to set the value of a thread-local variable.

Picture captions

Execute the process

  1. Get current thread
  2. Get the ThreadLocalMap of the thread, which shows that each thread is independent, so this method is naturally thread-safe
  3. Check whether the map is null
    • If not, K.V assigns the value k to this, which is the current ThreaLocal object
    • If yes, a ThreadLocalMap is initialized to maintain the K.V pair

Let’s look specifically at the set in ThreadLocalMap

5.2 ThreadLocalMap# set

private void set(ThreadLocal
        key, Object value) {
    // The new reference points to table
    Entry[] tab = table;
    int len = tab.length;
    // Get the index of the corresponding ThreadLocal in the table. Note that this is hashCode with a power of 2 length -1.
    int i = key.threadLocalHashCode & (len-1);

    /** * loops through * 1 from the subscript. If the same key exists, replace value * 2 directly. If the key has been reclaimed, replace the invalid key */
    for(Entry e = tab[i]; e ! =null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<? > k = e.get();// Find ThreadLocal with the same memory address and replace it directly
        if (k == key) {
            e.value = value;
            return;
        }
        // If k is null, ThreadLocal is cleaned up and the current invalid k is replaced
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return; }}// Find the space, create the node, and insert it
    tab[i] = new Entry(key, value);
    // The size of the element in the table increases
    int sz = ++size;
    // When the threshold (two-thirds of the array size) is reached, the expansion is performed
    if(! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }Copy the code

Note that if hashCode has a value at index I, it will start at I and continue to search through +1 until it finds an empty index and places the current ThreadLocal as the key.

6 get

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 is not empty
    if(map ! =null) {
        // Get the Entry of the current ThreadLocal object
        ThreadLocalMap.Entry e = map.getEntry(this);
        // If not null, the value saved in the current ThreadLocal is read
        if(e ! =null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            returnresult; }}// Otherwise, setInitialValue is executed
    return setInitialValue();
}
Copy the code

private T setInitialValue(a) {
    // Get the initial value, usually subclass override
    T value = initialValue();

    // Get the current thread
    Thread t = Thread.currentThread();

    // Get the ThreadLocalMap of the current thread
    ThreadLocalMap map = getMap(t);

    // If map is not null
    if(map ! =null)

        // Call ThreadLocalMap's set method for assignment
        map.set(this, value);

    // Otherwise create a ThreadLocalMap for assignment
    else
        createMap(t, value);
    return value;
}
Copy the code

And then let’s see

ThreadLocalMap#getEntry

// Get the value of the current thradLocal. The type of the value is determined by the thradLocal generics
// thradLocalMap get is the same as thradLocalMap set
// First try using hashcode to find the module size -1 = index position I, if not, spin I +1, until the index position is not empty
private Entry getEntry(ThreadLocal
        key) {
    // Calculate the index position: ThreadLocal's hashCode takes module size -1
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    // The ThreadLocal of e is not empty, and the memory address of the ThreadLocal of e is the same as that of the key
    if(e ! =null && e.get() == key)
        return e;
    else
    // The value of an array index is set
        return getEntryAfterMiss(key, i, e);
}
// Spin I +1 until found
private Entry getEntryAfterMiss(ThreadLocal<? > key,int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
    // When using ThreadLocal with different keys in large numbers, it can be quite a performance drain
    while(e ! =null) { ThreadLocal<? > k = e.get();// The memory address is the same as the memory address
        if (k == key)
            return e;
        // Delete unnecessary keys
        if (k == null)
            expungeStaleEntry(i);
        // Keep the index position + 1
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}
Copy the code

6 capacity

When the number of threadLocalMaps exceeds the threshold, the ThreadLocalMap starts to expand.

private void resize(a) {
    // Take out the old array
    Entry[] oldTab = table;
    int oldLen = oldTab.length;
    // The new array is twice the size of the old one
    int newLen = oldLen * 2;
    // Initialize the new array
    Entry[] newTab = new Entry[newLen];
    int count = 0;
    // Copy the values of the old array to the new array
    for (int j = 0; j < oldLen; ++j) {
        Entry e = oldTab[j];
        if(e ! =null) { ThreadLocal<? > k = e.get();if (k == null) {
                e.value = null; // Help the GC
            } else {
                // Computes the position of ThreadLocal in the new array
                int h = k.threadLocalHashCode & (newLen - 1);
                // if h is not empty, proceed by +1 until a null index is found
                while(newTab[h] ! =null)
                    h = nextIndex(h, newLen);
                // Assign to the new arraynewTab[h] = e; count++; }}}// Initialize the next expansion threshold for the new array, which is two thirds of the array length
    setThreshold(newLen);
    size = count;
    table = newTab;
}
Copy the code

Source code annotations are also relatively clear, we pay attention to two points:

The size of the array is twice as large as the original array. ThreadLocalMap is a property of a thread. A thread can only operate on ThreadLocalMap at any one time, because the same thread must execute the business logic in serial. Then the operation ThreadLocalMap must also be serial.

7 summary

ThreadLocal is a very important API that we often use when writing middleware, such as passing context in a process engine, passing ID in a call chain, etc. It is very useful, but has a lot of bugs.