ThreadLocal class annotation

ThreadLocal ThreadLocal ThreadLocal ThreadLocal

This class provides thread-local variables. These variables differ from

their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

Translation: ThreadLocal provides a set of thread-local (thread-owned) variables, which differ from each other when accessing data from the same ThreadLocal object (via ThreadLocal’s get or set methods) in each thread. Each thread has its own separate instance of a data variable (that is, a ThreadLocal object can fetch objects unique to each thread when different threads get). Instances of ThreadLocal are typically set to private static to keep some state (such as a user number or transaction number) associated with a thread.

😅, their normal counterparts refer to these variables, so there is a difference between them. Most of us are independently initialized to copy of the variabl, and its refers to each thread.

How to implement “each thread has its own independent initialization variable”, continue to see the comment gives an example:

public class ThreadId { // Atomic integer containing the next thread ID to be assigned // private static final AtomicInteger nextId = new AtomicInteger(0); // Thread local variable containing each thread s ID private static final ThreadLocal<Integer> threadId = new ThreadLocal<>() { @Override protected Integer initialValue() { return nextId.getAndIncrement(); }}; // Returns the current thread's unique ID, assigning it if necessary public static int get() { return threadId.get(); }}Copy the code

This example sets a separate ID for each Thread and “independently initializes” the variables by overriding the initialValue method. This method is triggered when the get method is called (if it has not previously called the set method to set the variables). This will be discussed later.

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the {@code ThreadLocal} instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

For a ThreadLocal object, each thread keeps an implicit reference of the object while it is alive, and this ThreadLocal object can be used to access data. When a thread dies, all references to ThreadLocal objects in the thread are submitted to the garbage collection (unless other references to ThreadLocal objects still exist).

Now that we have a sense of what ThreadLocal looks like, let’s take a look at its data structure.

ThreadLocal data structure

For the initialValue method in the above example, this is triggered when we first call the get method in the thread:

public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // If map is not empty, find the corresponding variable (if get, set, map is not empty) if (map! = null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e ! = null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; }} // If null, set the predefined initialization variable (usually the first time the current thread calls the ThreadLocal object's get method) return setInitialValue(); } ThreadLocalMap getMap(Thread t) {return. } private T setInitialValue() {// Call initialValue, generate the predefined variable T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // Save this variable to map if (map! = null) map.set(this, value); Else create the threadLocals object createMap(t, value); return value; } // If you need predefined variables, inherit ThreadLocal and override the method. Protected T initialValue() {return null; } // create ThreadLocalMap void createMap(Thread t, t firstValue) {t.htreadlocals = new ThreadLocalMap(this, firstValue); }Copy the code

As you can see, the get method uses threadLocals to save data, which means that ThreadLocal does not store data itself, but on the corresponding Thread. Let’s look at this variable in Thread:

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
Copy the code

ThreadLocalMap = ThreadLocalMap = ThreadLocalMap = ThreadLocalMap

ThreadLocalMap is a customized hash map suitable only for maintaining thread local values. No operations are exported outside of the ThreadLocal class. The class is package private to allow declaration of fields in class Thread. To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys. However, since reference queues are not used, stale entries are guaranteed to be removed only when the table starts running out of space.

It means: ThreadLocalMap is a custom hash map that only maintains thread-local data. It operates only within ThreadLocal (which is a static inner class of Package Private) and is packaged with Thread. You can declare the ThreadLocalMap variable in it. To facilitate memory reclamation, the Key of the Hash map here uses WeakReferences. However, when the key is no longer referenced elsewhere, the dirty data is only removed when ThreadLocalMap space is running out (during rehash).

Let’s just focus on its data structure for now.

This so-called hash map uses an array to store data:

static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<? >> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<? > k, Object v) { super(k); value = v; } } private Entry[] table; }Copy the code

The name of this array is table, and the element of the array is Entry, which is a key-value pair. Key uses WeakReference to reference ThreadLocal variable, and value is the data we save. So, ThreadLocalMap is essentially an array of key-value pairs. WeakReference is an implicit reference. When a ThreadLocal object is not referenced elsewhere, it can be reclaimed normally.

A ThreadLocal object uses itself as an index and binds the value of the requested variable to form an Entry pair, which is stored in the Thread’s ThreadLocalMap.

The figure above shows two ThreadLocal objects, each holding three values in three threads. Note that a ThreadLocal object can hold only one value in one thread, as explained in the previous section. To do this, let’s examine the methods that ThreadLocal and ThreadLocalMap use to access data.

1, ThreadLocalMap#set & ThreadLocalMap#set

Let’s start with the set method of ThreadLocalMap:

private void set(ThreadLocal<? > key, Object value) { Entry[] tab = table; int len = tab.length; Int I = key.threadLocalHashCode & (len-1); int I = key.threadLocalHashCode & (len-1); For (Entry e = TAB [I]; for (Entry e = TAB [I]; e ! = null; E = TAB [I = nextIndex(I, len)]) { > k = e.get(); If (k == key) {e.value = value; if (k == key) {e.value = value; return; If (k == null) {replaceStaleEntry(key, value, I); if (k == null) {replaceStaleEntry(key, value, I); return; }} // If the above loop iterates over an empty element, create an Entry TAB [I] = new Entry(key, value); // length +1 int sz = ++size; If (!) {// If (!) {// If (!) {// If (! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }Copy the code

As you can see from the above code, the value is replaced by a key that already exists in the array, and as you can see from the ThreadLocal set method, the passed key is the current ThreadLocal object:

public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! = null) // Pass the current object itself map.set(this, value); else createMap(t, value); }Copy the code

For a ThreadLocal object, the key is fixed, the current ThreadLocal object; Since there is only one key, a ThreadLocal object can store only one value in a thread, and each value can be stored in each thread, so that each value is independent of each thread.

ThreadLocalMap#getEntry & ThreadLocalMap#get

ThreadLocalMap getEntry method:

private Entry getEntry(ThreadLocal<? Int I = key.threadLocalHashCode & (table.length-1); Entry e = table[i]; if (e ! = null && e.get() == key) return e; Return getEntryAfterMiss(key, I, e); return getEntryAfterMiss(key, I, e); }Copy the code

This method is very simple, directly fetch the relevant data from the table, if the corresponding subscript data is inconsistent, it will traverse the table to find the corresponding key. The ThreadLocal get method was mentioned at the beginning and won’t be covered again.


Now that you have an overview of the structure of ThreadLocal, let’s take a closer look.

Other key points of ThreadLocal

1, How is the index of the table generated

In the set method:

int i = key.threadLocalHashCode & (len-1);
Copy the code

Key is the current ThreadLocal object, threadLocalHashCode is the variable in ThreadLocal, obtained by nextHashCode:

/**
 * ThreadLocals rely on per-thread linear-probe hash maps attached
 * to each thread (Thread.threadLocals and
 * inheritableThreadLocals).  The ThreadLocal objects act as keys,
 * searched via threadLocalHashCode.  This is a custom hash code
 * (useful only within ThreadLocalMaps) that eliminates collisions
 * in the common case where consecutively constructed ThreadLocals
 * are used by the same threads, while remaining well-behaved in
 * less common cases.
 */
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;

private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}
Copy the code

We see that nextHashCode is a static AtomicInteger. Each ThreadLocal object nextHashCode is incresed with a fixed value. Each ThreadLocal object gets a different threadLocalHashCode.

The comments on threadLocalHashCode say what they are: ThreadLocal relies on a linear hash map in each thread, with the ThreadLocal object as the key and its own subscript from threadLocalHashCode. This custom hash code maximizes the exclusion of hash collisions (collisions that produce the same subscript) when multiple contiguously created ThreadLocal objects are holding data in the same thread.

Next I want to experiment by setting the initial data using the ThreadLocalMap constructor:

/** * The initial capacity -- MUST be a power of two. */ private static final int INITIAL_CAPACITY = 16; ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); }Copy the code

The initial length of the table is 16 (the array length is required to be a multiple of 2, which is convenient for calculation. For example, the binary of 16-1 is 1111, which is convenient for the operation of &. For example, 1111010&1111 = 001010, which is 4 bits lower). If you create ThreadLocal objects of the same length as table, and all hold data in the same thread, what are their subscripts?

private static final int INITIAL_CAPACITY = 16; private static final int HASH_INCREMENT = 0x61c88647; Public static void main(String[] args) throws Exception {// Create an AtomicInteger Nexthashcode.get () = 0 AtomicInteger nextHashCode = new AtomicInteger(); // Test the hash int threadLocalHashCode; For (int j = 0; j < INITIAL_CAPACITY; ThreadLocalHashCode = nexthashCode.getAndAdd (HASH_INCREMENT); System.out.println("threadLocalHashCode: " + threadLocalHashCode); Int index = threadLocalHashCode & (INITIAL_CAPACITY - 1); System.out.println("index: " + index); } // j within INITIAL_CAPACITY, the index generated is not repeated}Copy the code

Let’s look at the output:

threadLocalHashCode: 0
index: 0
threadLocalHashCode: 1640531527
index: 7
threadLocalHashCode: -1013904242
index: 14
threadLocalHashCode: 626627285
index: 5
threadLocalHashCode: -2027808484
index: 12
threadLocalHashCode: -387276957
index: 3
threadLocalHashCode: 1253254570
index: 10
threadLocalHashCode: -1401181199
index: 1
threadLocalHashCode: 239350328
index: 8
threadLocalHashCode: 1879881855
index: 15
threadLocalHashCode: -774553914
index: 6
threadLocalHashCode: 865977613
index: 13
threadLocalHashCode: -1788458156
index: 4
threadLocalHashCode: -147926629
index: 11
threadLocalHashCode: 1492604898
index: 2
threadLocalHashCode: -1161830871
index: 9
Copy the code

Amazingly, within the length of the table array, the resulting index is not duplicated, which is the effect of no hash collisions.

2. Why use weak references for keys

This is simple, mainly for ThreadLocal object collection.

But, but, but watch out for a memory leak:

The principle of ThreadLocal is to operate a ThreadLocalMap inside a Thread. The Entry of this Map inherits WeakReference. After setting the value, there is a data structure such as (WeakReference,value) in the Map. Weak references in Java are reclaimed when memory runs out. After reclaimed, they take the form of (null,value) and the key is reclaimed.

If the thread is destroyed after execution, the value is also reclaimed and there is no memory leak. However, if it is in a thread pool, the thread is not recycled after execution, but is returned to the thread pool. In this case, Thread has a strong reference to ThreadLocalMap, and ThreadLocalMap has a strong reference to Entry. As a result, the value in the Entry whose key is null cannot be reclaimed and remains in the memory. Use threadlocale.remove () to remove unwanted data after executing the threadlocale.set () method to avoid memory leaks.

3. Looper saves using ThreadLocal

I believe you saw the source code when:

// sThreadLocal.get() will return null unless you ve called prepare(). static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() ! = null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); } /** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */ public static @Nullable Looper myLooper() { return sThreadLocal.get(); }Copy the code

If you understand how ThreadLocal works, the above code should be understood in seconds ~~😉


I have also written two analysis articles about Handler, which can take you through the source code step by step:

  • Handler (on) use method and operation principle
  • Synchronization barrier with IdleHandler

Finally, eat a wave of fruit ~~ 🍇🍈🍉🍊🍋🍌🍍🍎🍏🍐🍑🍒🍓