ThreadLocal Core Source Resolution

1 Preface This class provides thread local va...
Characteristic
5.1 ThreadLocal#set
5.2 ThreadLocalMap#set
ThreadLocalMap#getEntry
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.

7 May 2020, 14:13 | Views: 9837

Add new comment

For adding a comment, please log in
or create account

0 comments