ThreadLocal Core Source Resolution

1 Preface

This class provides thread local variables.These variables are different from regular variables because each thread accessing a variable through its get or set method has its own, independently initialized copy of the variable.A ThreadLocal instance is usually a private static field in a class that expects the state to be associated with a thread (for example, user ID or transaction ID).

For example, the following class generates a unique local identifier for each thread.The ID of the thread is assigned on the first call to ThreadId.get(), and remains unchanged on subsequent calls.


 
As long as the thread is active and the ThreadLocal instance is accessible, each thread has an implicit reference to a copy of its thread local variable.When the thread disappears, all copies of the thread's local instance will be GC (unless there are other references to those copies).

2 Continuing System

  • Inheritance? No, it's also a tool class under the java.lang package
  • However, the ThreadLocal definition has a generic meaning that data in any format can be stored.

3 Attributes

  • ThreadLocal relies on a thread linear probe hash table attached to each thread (Thread.threadLocals and InheritableThreadLocals).
    The threadlocal object acts as the key and searches through threadLocalHashCode.This is a custom hash code (useful only in ThreadLocalMaps) that eliminates conflicts in common situations where threadlocals are constructed continuously on the same thread and still perform well in less common cases.

One sentence summary: ThreadLocal calculates the current ThreadLocal index in ThreadLocalMap using such hashCode

  • The difference between consecutively generated hash codes, which can be set with reference to the article ThreadLocal hash algorithm (about 0x61c88647)
  • Notice the static decoration that ThreadLocalMap is set by multiple ThreadLocals, which are distinguished by the threadLocalHashCode

4 ThreadLocalMap

ThreadLocalMap is a custom hash table that only applies to maintaining thread-local values.No operations were exported outside the ThreadLocal class.
This class is package private and allows field declarations in the Thread class.To help handle very long lifetimes, hash table nodes use WeakReferences as the key.However, since reference queues are not used, deleting obsolete nodes is guaranteed only when there is insufficient table space.

static class ThreadLocalMap {

        /**
         * Nodes in this hash table use their primary reference field as the key (always a ThreadLocal object)
         * Inherited WeakReference. 
         * Note that an empty key (entry.get()== null) means that the key is no longer referenced, so the node can be deleted from the table.             
         * In the code below, this type of node is 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 Arrays, expanding if necessary
         * table.length Must be a power of 2
         */
        private Entry[] table;

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

        /**
         * Threshold for next capacity expansion
         */
        private int threshold; // Default 0

Characteristic

  • key is a reference to ThreadLocal
  • Value is a ThreadLocal saved value
  • Data structure of arrays

5 set

5.1 ThreadLocal#set

Sets the current thread copy of the thread local variable to the specified value.Most subclasses will not need to override this method, relying only on the initialValue method to set the value of thread local variables.

Execute process

  1. Get Current Thread
  2. Get the ThreadLocalMap for each thread, and you can see that each thread is independent, so this method is thread safe naturally
  3. Determine if map is null

    • No, K.V pair assignment, k is this, the current ThreaLocal object
    • Yes, initialize a ThreadLocalMap to maintain K.V pairs

Take a closer look at the set in ThreadLocalMap

5.2 ThreadLocalMap#set

private void set(ThreadLocal<?> key, Object value) {
    // New reference points to table
    Entry[] tab = table;
    int len = tab.length;
    // Get the index of the corresponding ThreadLocal in the table, and note that this is hashCode and power length-1 (remember why this calculation is better?)
    int i = key.threadLocalHashCode & (len-1);

    /**
     * Loop traversal from this subscript
     * 1,Replace value directly if the same key is encountered
     * 2,If the key has been invalidated by recycling, 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, replacing the currently invalid k
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    // Locate empty space, create node and insert
    tab[i] = new Entry(key, value);
    // The element size within the table increases by itself
    int sz = ++size;
    // When the threshold (two-thirds of the size of the array) is reached, expansion is performed
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

Note that if the index location i calculated by hashCode already has a value, it will start with i and continue searching backwards through + 1 until it finds a place where the index location is empty, placing the current ThreadLocal as the key.

6 get

public T get() {
    // Get Current Thread
    Thread t = Thread.currentThread();
    // Gets the ThreadLocalMap corresponding to the current thread
    ThreadLocalMap map = getMap(t);

    // If map is not empty
    if (map != null) {
        // Get the Entry corresponding to the current ThreadLocal object
        ThreadLocalMap.Entry e = map.getEntry(this);
        // If not empty, read the values saved in the current ThreadLocal
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // Otherwise, set InitialValue is executed
    return setInitialValue();
}
private T setInitialValue() {
    // Get the initial value, typically a subclass override
    T value = initialValue();

    // Get Current Thread
    Thread t = Thread.currentThread();

    // Gets the ThreadLocalMap corresponding to the current thread
    ThreadLocalMap map = getMap(t);

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

        // Call the set method of ThreadLocalMap for assignment
        map.set(this, value);

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

Next let's look at it

ThreadLocalMap#getEntry

// Gets the value corresponding to the current thradLocal whose type is determined by the generic type of thradLocal
// Because thradLocalMap set resolves array index position conflict logic, thradLocalMap get logic also corresponds
// First try searching based on hashcode modular array size -1 =index position i, if not found, spin handle i+1 until it finds that the index position is not empty
private Entry getEntry(ThreadLocal<?> key) {
    // Compute index position: ThreadLocal hashCode modular array size-1
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    // E is not empty, and e's ThreadLocal has the same memory address as the key and returns directly, otherwise it is not found and continues to be found by the getEntryAfterMiss method
    if (e != null && e.get() == key)
        return e;
    else
    // This logic for fetching data is due to an array index position conflict during set time  
        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 a large number of different key s, it's a performance-intensive process
    while (e != null) {
        ThreadLocal<?> k = e.get();
        // Memory address is the same, indicating that it was found
        if (k == key)
            return e;
        // Delete useless key s
        if (k == null)
            expungeStaleEntry(i);
        // Continue indexing position + 1
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}

6 Expansion

When the number of ThreadLocal in ThreadLocalMap exceeds the threshold, ThreadLocalMap will begin to expand. Let's look at the logic of expansion:

private void resize() {
    // 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 a new array
    Entry[] newTab = new Entry[newLen];
    int count = 0;
    // Copy the value 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 {
                // Calculate ThreadLocal's position in the new array
                int h = k.threadLocalHashCode & (newLen - 1);
                // If the position value of index h is not empty, go back + 1 until an index position with empty value is found
                while (newTab[h] != null)
                    h = nextIndex(h, newLen);
                // Assigning values to new arrays
                newTab[h] = e;
                count++;
            }
        }
    }
    // Initialize the next expansion threshold for a new array, which is two-thirds the length of the array
    setThreshold(newLen);
    size = count;
    table = newTab;
}

Source notes are also clear, so there are two points to note:

The size of the expanded array is twice that of the original array.
ThreadLocalMap is an attribute of threads. ThreadLocalMap can only operate on ThreadLocalMap at one time because the same thread must execute business logic serially, so the operation of ThreadLocalMap must also be serial.

7 Summary

ThreadLocal is a very important API. It is often used when writing a middleware, such as context transfer in process engine, transfer of call chain ID, etc. It is very useful, but there are many pits.

Tags: Java less Attribute

Posted on Thu, 07 May 2020 14:13:51 -0400 by iwarlord