The threadLocal class essentially swaps space for time, so that each thread has a copy of a shared variable. Then there is no problem with multi-threaded concurrency. Each thread can modify its own copy of a variable independently.
The properties of ThreadLocal
ThreadLocal is a very simple property called nextHashCode, and there’s a magic HASH_INCREMENT variable that gets the nextHash value every time you increment this fixed number, It is better to add a fixed magic number to an array of 2 to the NTH power of hashCode than to add a fixed magic number to an array of 2 to the n power of HashCode
private final int threadLocalHashCode = nextHashCode();
/**
* The next hash code to be given out. Updated atomically. Starts at
* zero.
*/
private static AtomicInteger nextHashCode =
new AtomicInteger();
/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647;
Copy the code
ThreadLocal’s set method
The get set method is a way to understand the core principles of threadLocal,
- First get the current thread,
- Get the variable ThreadLocalMap, the internal object of Thread,
- If the ThreadLocalMap variable is empty, a ThreadLocalMap object is created. Note that this is an example of threadLocal
- If the ThreadLocalMap variable is not empty, the value is set directly
Public void set(T value) {public void set(T value) { ThreadLocalMap map = getMap(t); if (map ! = null) {// Set map.set(this, value); } else {// create createMap(t, value); Void createMap(Thread t, t firstValue) {t.htreadlocals = new ThreadLocalMap(this, firstValue); }Copy the code
ThreadLocalMap class member variable
As can be seen from the following, the array object of ThreadLocalMap is an Entry object, which inherits WeakReference. When initializing the Entry object, the constructor of the parent class is called, that is, k in the Entry object is a WeakReference, and when creating the object above, The argument passed in is the this pointer to the ThreadLocal object, so weak references are placed into the Entry object of the ThreadLocalMap.
static class ThreadLocalMap { /** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference<ThreadLocal<? >> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<? > k, Object v) { super(k); value = v; } } /** * The initial capacity -- MUST be a power of two. */ private static final int INITIAL_CAPACITY = 16; /** * The table, resized as necessary. * table.length MUST always be a power of two. */ private Entry[] table; /** * The number of entries in the table. */ private int size = 0; /** * The next size value at which to resize. */ private int threshold; // Default to 0Copy the code
The relationship between Thread and ThreadLocalMap and ThreadLocal is as follows:
In a ThreadLocalMap, the key is a ThreadLocal object, and the value is a stored variable value. The two forms an Entry object, which is set to the ThreadLocalMap. The solution is to develop the address method, that is, to the right offset. A HashMap is a zipper method, and that’s just one of the differences, but you can read on for the rest.
- If there is a collision, open address method is used to offset the bucket address by index + 1. If key is found to be k, the new value is replaced by the old value and returned.
- If the key is null during the query, replaceStaleEntry is called to replace the old Entry.
- If the position of I is empty, create a new Entry and place it at the position of I,size plus 1.
- Call cleanSomeSlots to clear some slots in the [I,size] interval, and call rehas to expand if the array length is greater than threshold(2/3 of the array).
private void set(ThreadLocal<? > key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e ! = null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<? > k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }Copy the code
Next, take a look at how replaceStaleEntry replaces the old Entry.
- SlotToExpunge marks an index, starting with the hash address &(len-1) at position I, and looking back to find an Entry that is not empty but has an empty key. So [slotToExpunge, I] is the index interval that needs to be cleaned.
- From the position behind staleSlot, we start to traverse from front to back. If we find this key, we can exchange this Entry to the position of staleSlot.
- If the start position of dirty data is equal to the cable position of slotToExpunge, the slotToExpunge index is assigned to the index that finds the first equal key position from the previous step forward. Then expungeStaleEntry is called to clean up the dirty data starting with staleSlot, and finally some slots are cleaned up with a cleanSomeSlots heuristic scan.
- If there is no cleared slot and the size is greater than threshod(2/3 of size). Rehash all arrays to clean up expired data. If threshod is still 3/4 of threshod, resize the array.
private void replaceStaleEntry(ThreadLocal<? > key, Object value, int staleSlot) { Entry[] tab = table; int len = tab.length; Entry e; // Back up to check for prior stale entry in current run. // We clean out whole runs at a time to avoid continual // incremental rehashing due to garbage collector freeing // up refs in bunches (i.e., whenever the collector runs). int slotToExpunge = staleSlot; for (int i = prevIndex(staleSlot, len); (e = tab[i]) ! = null; i = prevIndex(i, len)) if (e.get() == null) slotToExpunge = i; // Find either the key or trailing null slot of run, whichever // occurs first for (int i = nextIndex(staleSlot, len); (e = tab[i]) ! = null; i = nextIndex(i, len)) { ThreadLocal<? > k = e.get(); // If we find key, then we need to swap it // with the stale entry to maintain hash table order. // The newly stale slot, or any other stale slot // encountered above it, Can then be sent to expungeStaleEntry // to remove or rehash all of the other entries in run. if (k == key) {// Swap places e.value = value; tab[i] = tab[staleSlot]; tab[staleSlot] = e; // Start expunge at preceding stale entry if it exists if (slotToExpunge == staleSlot) slotToExpunge = i; cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; } // If we didn't find stale entry on backward scan, the // first stale entry seen while scanning for key is the // first still present in the run. if (k == null && slotToExpunge == staleSlot) slotToExpunge = i; } // If key not found, put new entry in stale slot tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); // If there are any other stale entries in run, expunge them if (slotToExpunge ! = staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }Copy the code
StaleSlot is the index of entries whose key is known to be empty. Start from staleSlot and search from front to back.
- If the key is empty, set the value of the Entry to NULL. And set the corresponding position to null if
- If the key is not empty and the I position is not equal to hash&(len-1), the element was put in by developing the address method, then rehash until the hash&(len-1) is empty. And put it in this position (this step is mainly to solve the problem of reducing hash collisions, so that the query time is O(1)).
private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // expunge entry at staleSlot tab[staleSlot].value = null; tab[staleSlot] = null; size--; // Rehash until we encounter null Entry e; int i; for (i = nextIndex(staleSlot, len); (e = tab[i]) ! = null; i = nextIndex(i, len)) { ThreadLocal<? > k = e.get(); if (k == null) { e.value = null; tab[i] = null; size--; } else { int h = k.threadLocalHashCode & (len - 1); if (h ! = i) { tab[i] = null; // Unlike Knuth 6.4 Algorithm R, we must scan until // null because multiple entries could have been stale. while (tab[h] ! = null) h = nextIndex(h, len); tab[h] = e; } } } return i; }Copy the code
Here is the search index I (expungeStaleEntry),n is the length of TAB, starting from I, if the Entry is not empty, But if the key is empty, save len, assign the removed variable to true, then call expungeStaleEntry to remove the dirty data again, then cut n in half and explore again until it reaches 0.
private boolean cleanSomeSlots(int i, int n) { boolean removed = false; Entry[] tab = table; int len = tab.length; do { i = nextIndex(i, len); Entry e = tab[i]; if (e ! = null && e.get() == null) { n = len; removed = true; i = expungeStaleEntry(i); } } while ( (n >>>= 1) ! = 0); return removed; }Copy the code
- Scan all data to clean expired data,
- If the size is greater than 3/4 of threshod, call resize to expand capacity.
private void rehash() {
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
if (size >= threshold - threshold / 4)
resize();
}
Copy the code
ThreadLocal resize For capacity expansion
The capacity expansion mechanism of ThreadLocal is to multiply the original size of the array by 2, recalculate the hash value, put it into the new array, and recalculate the capacity expansion threshold.
private void resize() { Entry[] oldTab = table; int oldLen = oldTab.length; int newLen = oldLen * 2; Entry[] newTab = new Entry[newLen]; int count = 0; 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 { int h = k.threadLocalHashCode & (newLen - 1); while (newTab[h] ! = null) h = nextIndex(h, newLen); newTab[h] = e; count++; } } } setThreshold(newLen); size = count; table = newTab; }Copy the code
ThreadLocal get gets the value
- Get the threadLocalMap object for Thread, and then get the Entry object using the getEntry method.
- If Entry is not empty, return,
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map ! = null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e ! = null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }Copy the code
* First use threadLocalHashCode&(len-1) for the key to locate the bucket.
- If the key is equal, the element has been found and is returned
- If the keys are different, then develop the address method and call getEntryAfterMiss to continue the search.
private Entry getEntry(ThreadLocal<? > key) { int i = key.threadLocalHashCode & (table.length - 1); Entry e = table[i]; if (e ! = null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); }Copy the code
- Continue searching from the position of I until entry is empty
- If k is null, expungeStaleEntry cleans up expired data
- If the loop is not found, null is returned
private Entry getEntryAfterMiss(ThreadLocal<? > key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e ! = null) { ThreadLocal<? > k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; }Copy the code
The remove value of ThreadLocal
- ThreadLocalHashCode &(len-1) is used to locate the array index I.
* Search from front to back starting from I until the keys are equal, then delete Entry, and execute expungeStaleEntry to finish a cleanup of expired data
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
Copy the code
Today’s summary is mainly to make a detailed analysis of ThreadLocal’s set get remove and other important methods, but also to solve the Hash conflict method of ThreadLocal, weak reference key, capacity expansion, probe cleaning and heuristic cleaning expired elements have a cleaning understanding.