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.
2 Continuation system
-
Inheritance? There is no such thing as a utility class in the java.lang package
-
However, ThreadLocal definitions come with generics, meaning that data can be stored in any format.
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
-
The difference between continuously generated hash codes. For setting this value, see article ThreadLocal’s Hash algorithm (0x61C88647).
-
Note the static modifier,ThreadLocalMap is set to multiple ThreadLocal, which is distinguished by threadLocalHashCode
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.
Execute the process
- Get current thread
- Get the ThreadLocalMap of the thread, which shows that each thread is independent, so this method is naturally thread-safe
- 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.