One, foreword

ThreadLocal is one of the more important topics in Java multithreaded modules. Although ThreadLocal is in the java.lang package, it is basically just for multithreading.

ThreadLocal class

2.1 Origin + Meaning + Application Scenario

Common variables are shared by multiple threads. If you want a variable to be shared exclusively by one thread, you need to use the ThreadLocal class

A ThreadLocal creates a copy of a variable in each thread. The copies owned by different threads do not affect each other.

Usage Scenarios:

  1. When transferring objects across layers, multiple transfer can be avoided and the constraints between layers can be broken.
  2. Data isolation between threads;
  3. Transaction operation, used to store thread transaction information;
  4. Database connection, Session Session management.

2.2 Using Layers: Thread isolation of ThreadLocal variables

Since the purpose of ThreadLocal is to create one copy per thread, let’s use an example to verify this. Create two threads, t1 set var value to 20, t2 set var value to 15, output var value respectively, the result is as follows:

Set () and get() are the main methods of ThreadLocal.

public class Demo { private static ThreadLocal<Integer> var = new ThreadLocal<>(); Public static void main(String[] args) {Thread t1 = new Thread(()->{var. Set (20); System.out.println(thread.currentThread ().getName() + ": set var to 20"); for (int i = 0; i < 3; I++) {system.out.println (thread.currentthread ().getname () + "; // The developer calls the get() method try {thread.sleep (1000); } catch (InterruptedException e) { e.printStackTrace(); } } }, "Thread1"); Thread t2 = new Thread(()->{ var.set(15); // call set() for (int I = 0; i < 3; I++) {system.out.println (thread.currentthread ().getname () + "; // // developer calls get() try {thread.sleep (1000); } catch (InterruptedException e) { e.printStackTrace(); } } }, "Thread2"); t1.start(); t2.start(); }}Copy the code

Running results:

Thread1: gets the var value to be 20 Thread2: gets the var value to be 15 Thread1: gets the var value to be 20 Thread2: gets the var value to be 15 Get the var value is 20Copy the code

From the results, we can see that the copies of the ThreadLocal class variables stored in different threads are not affected by each other.

ThreadLocal = ThreadLocal

Navigate to the ThreadLocal class, which is in the java.lang package.

3.1 Trunk method, for developers to use: set() method

3.1.1 Interpretation of set() method

public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // Get Map if (Map! Map. set(this, value); Else // createMap createMap(t, value) if map is empty; }Copy the code

Set ()

  1. Gets the current Thread and the ThreadLocalMap instance of the current Thread (as can be easily seen from getMap(Thread t)).
  2. If the obtained map instance is not empty, call map.set(); otherwise, call createMap(t, value) to instantiate the map.

3.1.2 Delving further into set(ThreadLocal Key, Object Value)

Map.set (this, value);

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); NextIndex (); nextIndex(); nextIndex(); nextIndex(); For (Entry e= TAB [I]; e ! = null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<? > k = e.get(); If (k == key) {e.value = value; if (k == key) {e.value = value; return; } // the key on table[I] is empty, indicating that it was reclaimed (weak reference). If (k == null) {replaceStaleEntry(key, value, I); if (k == null) {replaceStaleEntry(key, value, I); // the branch method explains the return; TAB [I] = new Entry(key, value); TAB [I] = new Entry(value); Int sz = ++size; // cleanSomeSlots is used to remove LLDB ()==null, i.e. Table [index]! =null && table[index].get()==null // The object associated with this data key has been reclaimed, so this Entry(table[index]) can be null. // Rehash () if (!) if no entry is cleared and the current usage has reached the size defined by the load factor (2/3 of the length) cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }Copy the code

During the insert, the ThreadLocal object is located to position I in the table according to the index value of the ThreadLocal object. The process is as follows:

  1. If the table[I] on the current index is empty (goldfinger: e==null, out of the loop), then just initialize an Entry object at position I.

  2. Unfortunately, position I already has an Entry object. If the key of this Entry object happens to be the key to be set, the value in the Entry is updated. If the key of an Entry object is null, the table[I] can be reused and replaced with a new key-value, and other invalid entries can be deleted.

  3. Continuous next loop: Unfortunately, the Entry object at position I has nothing to do with the key to be set, so it has to find the next empty position;

3.1.3 Further, threadLocalHashCode variable

private static final int HASH_INCREMENT = 0x61c88647;
private final int threadLocalHashCode = nextHashCode();

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

Each ThreadLocal object has a hash value of threadLocalHashCode, and each time a ThreadLocal object is initialized, the hash value increases by a fixed size 0x61C88647. This value is related to Fibonacci hash. The principle of Fibonacci hash is not discussed here. The main purpose of Fibonacci hash is to evenly distribute the hash code in the 2 ^ N array, that is, Entry[] table.

ThreadLocalMap uses linear probing to resolve hash collisions with address increments di = 1, 2… , m-1, where, I is the number of detection. This method probes one address at a time until an empty address is inserted. If no free address can be found in the entire space, an overflow occurs.

Assume that the length of the current table is 16, that is, if the hash value of the calculated key is 14, if the table[14] already has a value and its key is inconsistent with the current key, then a hash conflict will occur. In this case, add 14 and 1 to get 15, and judge by taking table[15]. At this time, if the conflict is still returned to 0, take table[0], and so on, until it can be inserted.

As described above, you can think of a table as an array of rings.

Let’s look at the linear probe code and see that the table is actually a ring:

Private static int nextIndex(int I, int len) {private static int nextIndex(int I, int len) {private static int nextIndex(int I, int len) {private static int nextIndex(int I, int len) { So the loop return (I + 1 < len)? i + 1 : 0); }Copy the code

As you can see, the set() method is inefficient if it conflicts heavily.

3.2 Trunk method for developers: get()

Get ()

T = thread.currentThread (); T = thread.currentThread (); ThreadLocalMap map = getMap(t); Good if (map! = null) { ThreadLocalMap.Entry e = map.getEntry(this); ThreadLocalMap.Entry if (e! + + + + = null) {// Return value (i.e. Threadlocalmap.entry) @suppressWarnings ("unchecked") T result = (T)e.value; return result; Return setInitialValue(); }Copy the code

For the get() method:

  1. Gets the current Thread and the ThreadLocalMap instance of the current Thread (as can be easily seen from getMap(Thread t)).
  2. If the obtained map instance is not empty, call map.getentry (this) to get the Entry object, otherwise call setInitialValue() to instantiate the map.

3.2.2 Drill further into the getEntry() method

private Entry getEntry(ThreadLocal<? Int I = key.threadLocalHashCode & (table.length-1); Entry e = table[i]; // Get entry from index I if (e! = null && LLDB () == key) / / return entry else / / three of the e = = null | | um participant et ()! Return getEntryAfterMiss(key, I, e); return getEntryAfterMiss(key, I, e) // Pass key in the method argument, pass I for key, pass e for I, sum up, key I entry}Copy the code

3.2.3 Further, the getEntryAfterMiss() method

GetEntryAfterMiss () = getEntryAfterMiss(); getEntryAfterMiss() = getEntryAfterMiss(); Return null private Entry getEntryAfterMiss(ThreadLocal<? > key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e ! = null) {LLDB () {LLDB () = LLDB ()! ThreadLocal<? > k = e.get(); // LLDB () if (k == key) return e; If (k == null) // If k in e is null, delete the invalid entry expungeStaleEntry(I); NextIndex (I, len); nextIndex(I, len); nextIndex(len); e = tab[i]; } return null; If e==null, return null}Copy the code

3.3 Trunk method for developers: remove()

Public void remove() {// Get a ThreadLocalMap based on the thread. ThreadLocalMap m = getMap(thread.currentThread ()); if (m ! = null) m.remove(this); } private void remove(ThreadLocal<? > key) { Entry[] tab = table; int len = tab.length; Int I = key.threadLocalHashCode & (len-1); Key for (Entry e = TAB [I]; e ! = null; E = TAB [I = nextIndex(I, len)]) {if (LLDB () = key) {e = TAB [I = nextIndex(I, len)]) {if (LLDB () = key) { // Clear expungeStaleEntry(I); return; } } } public void clear() { this.referent = null; // The code level is set to null. If set to null, the Java memory reclamation will be handled.Copy the code

Remove () can be said to be very simple after the above understanding, that is, find the corresponding table[], call WeakRefrence’s clear() to clear the reference, and then call expungeStaleEntry() to clear it.

3.4 Branch method, auxiliary three trunk methods set() get() remove()

3.4.1 When ThreadLocalMap is initialized (used in 3.1set() 3.2 Get ())

First, if ThreadLocalMap is null when set() is called for the first time, the createMap(t, value) method is called to initialize the ThreadLocalMap

If ThreadLocalMap is null when set() is called for the first time, createMap(t, value) is called to initialize the ThreadLocalMap.

void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } // 1, create an Entry array of INITIAL_CAPACITY size as table // 2, insert firstKey, FirstValue // 3. Set the capacity expansion threshold to two-thirds of the initial capacity. > firstKey, Object firstValue) {// Initialize an Entry array of size 16 // The size of the table is always a power of 2. / / calculate the hash key int I = firstKey. ThreadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; // Set the capacity expansion threshold setThreshold(INITIAL_CAPACITY); } private void setThreshold(int len) { threshold = len * 2 / 3; // The threshold is two-thirds}Copy the code

For & (INITIAL_CAPACITY – 1), this is a way of modulo, taking a power of 2 as a modulo instead of %(2^n). This is why the capacity of an Entry must be a power of 2.

Second, if ThreadLocalMap is null when the get() method is called, the setInitialValue() method is called to initialize the ThreadLocalMap, and createMap(t, value) is actually called

If ThreadLocalMap is null, setInitialValue() will be called to initialize ThreadLocalMap. CreateMap (t, value) will be called as well.

Private T setInitialValue() {// get the initialValue, default is null(if there is no subclass to override) T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); Set if (map! = null) map.set(this, value); CreateMap (t, value); // createMap(t,null); good return value; } protected T initialValue() { return null; }Copy the code

3.4.2 replaceStaleEntry replaces invalid Entry(only used: used in 3.1set() method)

ReplaceStaleEntry replaces invalid Entry(only used: used in 3.1set() method)

private void replaceStaleEntry(ThreadLocal<? > key, Object value,int staleSlot) {// key and value are new values to set // staleSlot is stale position, k==null, To replace the swap element Entry[] TAB = table; int len = tab.length; Entry e; /** * Scan forward * contiguous entries (contiguous means contiguous entries and table[I]!) based on the location of the incoming invalid entry(staleSlot) / int slotToExpunge = staleSlot; / int slotToExpunge = staleSlot; For (int I = prevIndex(staleSlot, len); (e = tab[i]) ! = null; I = prevIndex(I, len)) if (LLDB () ==null) // Scan a sequence of entries for (int I = nextIndex(staleSlot, len); (e = tab[i]) ! = null; i = nextIndex(i, len)) { ThreadLocal<? > k = e.get(); If (k == key) {e.value = value; if (k == key) {// E.value = value; // set value to TAB [I]. Value, e.value, TAB [staleSlot] = e; // Set value to TAB [I]. tab[i] = tab[staleSlot]; TAB [I] TAB [staleSlot] = e; // slotToExpunge if no invalid entry is found, I if (slotToExpunge == staleSlot) CleanSomeSlots (expungeStaleEntry(slotToExpunge), len); return; } // If the forward search does not find an invalid entry, and the current backscan entry is invalid, Then update slotToExpunge to the current value I if (k == null && slotToExpunge == staleSlot) slotToExpunge = I; // The forward scan and the backward scan have no results} // Break out of the forward and backward cycle, E ==null e=null // If no key is found, that is, the key does not exist in the table before // Add the original invalid entry -- TAB [staleSlot] directly tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); // slotToExpunge ! SlotToExpunge // The cleanSomeSlots() method is called if (slotToExpunge! = staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); / / clean up}Copy the code

3.4.3 cleanSomeSlots cleans slot heuristically (only three calls: replaceStaleEntry() twice and set() once)

/** * @param I scans backwards from I (excluding I because Slot I is null) * @param n controls the number of scans, normally log2(n), * If an invalid entry is found, n is reset to len, the length of the table, to clear segments. * Map.set () is called with the number of elements, Len */ private Boolean cleanSomeSlots(int I, int n) {Boolean removed = false; Entry[] tab = table; int len = tab.length; do { i = nextIndex(i, len); // Get the next Entry from I e = TAB [I]; // Get e Entry from I e = TAB [nextIndex(I, len)]; if (e ! =null && LLDB () ==null) {key==null // set n to len, n = len; removed = true; I = expungeStaleEntry(I); I = expungeStaleEntry(I); // the expungeStaleEntry method returns an I}} while ((n >>>= 1)! = 0); // Unsigned right movement can be used to control the number of scans in log2(n) return removed; //true delete false not delete}Copy the code

Normally, if no invalid slot is found in log n scans, the function ends and returns false. If an invalid slot is found, set n to len of the table, clear consecutive segments, and continue scanning from the next empty slot.

Goldfinger: If an invalid slot is found, set n to the length len of the table, clean the contiguous segment, and continue scanning from the next empty slot.

This function is called in two places: map.set() is called and replaceStaleEntry() is called when invalid slot is replaced. N is passed in for the number of elements, and replaceStaleEntry() is called for the size of the table.

3.4.4 expungeStaleEntry Sequential cleanup (called in multiple methods: cleanSomeSlots expungeStaleEntries getEntryAfterMiss remove replaceStaleEntry)

ExpungeStaleEntry expunge Is cleared. StaleEntry is invalid

ExpungeStaleEntry: delete consecutive segments.

Table [staleSlot]; * 2, and according to the current incoming staleSlot, scan a row of contiguous entries (contiguous refers to a row of adjacent entries and table[I]! = null), * 3. Rehash entries that may have hash conflicts. * * @param staleSlot key is null, which requires the index in the table where the invalid entry is located * @return returns the index of the next solt that is empty. */ private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; TAB [staleSlot]. Value = null; tab[staleSlot] = null; // select * from table where table = 1; // select * from table where table = 1; // Rehash until we encounter null // Rehash until null Entry e; int i; For (I = nextIndex(staleSlot, len); for (I = nextIndex(staleSlot, len); // Use staleSlot for the first time to get the next I (e = TAB [I])! = null; // e! NextIndex (I, len)) {ThreadLocal<? > k = e.get(); If (k ==null) {if (k ==null e.value =null; if (k ==null) { tab[i] = null; size--; } else {// If key is not null, calculate index int h = k.htReadLocalHashCode & (len-1); /** * if (h!) */ * if (h!) */ * if (h!) = i) { tab[i] = null; // while (tab[h] ! = null) h = nextIndex(h, len); tab[h] = e; }}} // Exit the loop is e==null, return e object I,expungeStaleEntry returns an e==null corresponding to I return I; }Copy the code

The traversal starts from staleSlot and cleans invalid keys (weak references pointing to objects being reclaimed), that is, sets the value in the corresponding entry to NULL, sets the table[I] pointing to this entry to NULL, until empty entries (e== NULL) are swept.

In addition, non-empty entries are rehashed during the process. The purpose of this function is to clear slot from staleSlot (disconnect strong references, rehash slot, etc.)

3.4.5 rehash(unique call to set())

General procedure: Clear all elements first. If the number of existing elements exceeds the load, expand the capacity.

Private void rehash() {// Do a full cleanup of expungeStaleEntries(); /** * threshold = 2/3 * len * so threshold-threshold / 4 = 1en/2 * */ if (size >= threshold-threshold / 4) // if (size >= 1en/2 resize(); / / expansion}Copy the code
Full cleanup of expungeStaleEntries()

“Expunge” is “clean”, “stale” is “invalid” and “stale” is “entry”

Private void expungeStaleEntries() {entry [] TAB = table; private void expungeStaleEntries() {entry [] TAB = table; int len = tab.length; for (int j = 0; j < len; j++) { Entry e = tab[j]; // TAB is an array of entries, so each TAB is an Entry object. = null && LLDB () == NULL) // Entry not null key is null // Clear expungeStaleEntry(j) with consecutive segments; // Clear entry with subscript j}}Copy the code

Goldfinger: expungeStaleEntries() and expungeStaleEntry() are two different methods. ExpungeStaleEntries () is a circular call, and expungeStaleEntry() does the real work

Capacity expansion (Capacity expansion creates new arrays, shuffles values, sets new thresholds and sizes, just like the capacity expansion of hashMap)

1, capacity expansion: because we need to ensure that the capacity of the table len is a power of 2, so capacity expansion is 2 times. 2, upset the value: oldTab value, calculate the subscript h to newTab. 3, set the new threshold and new size: last step

Private void resize() {Entry[] oldTab = table; private void resize() {Entry[] oldTab = table; Int oldLen = oldtab.length; int newLen = oldLen * 2; // Double Entry[] newTab = new Entry[newLen]; // But now newTab is empty int count = 0; for (int j = 0; j < oldLen; ++j) { Entry e = oldTab[j]; // Each oldTab value if (e! = null) { ThreadLocal<? > k = e.get(); // Despite a cleanup of the expungeStaleEntries() method, If (k ==null) {// Set e.value to null; } else {// Also use linear probe to set values int h = k.htreadLocalHashCode& (newlen-1); while (newTab[h] ! = null) h = nextIndex(h, newLen); newTab[h] = e; // newTab[h] == null, set count++; // set newTab, count++}}} // set new threshold setThreshold(newLen); // Pass newLen and set the new threshold to 2/3. // count sets the class variable size table = newTab; // newTab has a value, put it in the table variable}Copy the code

3.4.6 Why is the Key of ThreadLocalMap a Weak Reference

Static class Entry extends WeakReference

If it is a strong reference, ThreadLocal cannot be freed from memory.

If you use the normal key-value form to define the storage structure, you will essentially make the node’s life cycle strongly tied to the thread. As long as the thread is not destroyed, the node will remain reachable in GC analysis and cannot be reclaimed, and the program itself will not be able to determine whether the node can be cleaned.

Weak references are the third tier of four-file references in Java and are weaker than soft references. If an object does not have a strong reference chain reachable, it will generally not survive the next GC. When a ThreadLocal has no strong reference reachable, the key value of the corresponding Entry in the ThreadLocalMap becomes invalid as it is garbage collected, which facilitates garbage cleaning of the ThreadLocalMap itself.

Auric goldfinger: ThreadLocal inner class is ThreadLocalMap, ThreadLocalMap inner class is the Entry

If a strong reference is used, the lifetime of the node will be tied to the thread. As long as the thread is not destroyed, the node will remain reachable in the GC analysis.

3.4.7 ThreadLocalMap principle

public class ThreadLocal<T> { 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; }}... }... }Copy the code

ThreadLocalMap is a hashMap-like data structure that does not implement a Map interface. An Entry array of size 16 is initialized internally. The Entry object is used to hold each key-value pair, except that the key is always a ThreadLocal object (tip: Entry(ThreadLocal<? > k, Object v).

When the set() method of a ThreadLocal object is called, the ThreadLocal object itself is treated as the key (tip: because the Entry key must be of type ThreadLocal), and the variable corresponding to the current thread is treated as the value (tip: A specific value, such as 20 or 15, is stored in ThreadLoalMap.

public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! Map.set (this, value); map.set(this, value); else createMap(t, value); }Copy the code

Tip: The Entry of ThreadLoalMap inherits WeakReference. The big difference between the Entry of ThreadLoalMap and Entry of HashMap is that there is no next field in the Entry of ThreadLoalMap, so there is no linked list.

3.4.8 Memory Leakage Problem

As you can see in the figure above, the key of the Entry object is a weak reference. When ThreadLocalMap is garbage collected, the corresponding Entry will not be collected because ThreadLocalMap is bound to the thread. The key of a ThreadLocalMap is missing, but the value still exists, which over time can cause memory leaks (tip: key is a weak reference, but value is a specific reference).

Get (), set(), get(), set(), get(), set(), get(), set(), get(), set()), get(), set(), get(), set()), get(), set()), get(), set(), set()), get(), set(), set()), get(), set()), get(), set())

private void set(ThreadLocal<? > key, Object value) { ... for (Entry e = tab[i]; . if (k == null) { replaceStaleEntry(key, value, i); return; }}... }Copy the code

In this way, no GC Roots can reach the corresponding value, and it can be recycled in the next GC. However, the get() and set() methods also have incomplete cleanup, so after using ThreadLocal, you need to call the remove() method to clean up.

The JDK recommends making the ThreadLocal variable private static. This way, the lifetime of a ThreadLocal is longer, and the ThreadLocal cannot be reclaimed because there is always a strong reference to it. This ensures that the Entry value can be accessed at any time based on ThreadLocal’s weak reference.

Summary: Priority: Strong Reference > OOM > Soft Reference > Weak Reference Goldfinger: Key is a weak reference, but value is a specific reference. ThreadLocalMap is tied to the thread and Entry does not disappear. Therefore, for key== NULL, set Entry to NULL so that the GC can collect it once

Interview Goldfinger

4.1 LocalThread class structure

LocalThread class structure

  1. ThreadLocalMap per Thread: Each Thread maintains a reference to a ThreadLocalMap, and copies of variables are stored in the Thread’s own ThreadLocalMap.

  2. ThreadLocalMap is an internal class of ThreadLocal that uses Entry to store key-values for ThreadLocal objects and values for thread variables.

  3. ThreadLocal is just a Key: A ThreadLocal itself does not store values; it simply acts as a key for the thread to get a value from a ThreadLocalMap.

4.2 Source Code Parsing

That’s what’s up there. Skip.

Five, the summary

The ThreadLocal class is fully parsed and done.

Play code every day, progress every day!!