ThreadLocal principle analysis
-
- 1. Basic concepts
-
- Stack & Heap
- ThreadLocal
- ThreadLocalMap
- Entry
- 2, source code core method analysis
-
- The get () the source code
- SetInitialValue () the source code
- Sequence diagram of get() method
- Set () the source code
- Remove () the source code
- Remove () method sequence diagram
- 3. Core algorithm
-
- Hash generating algorithm
- Index generation algorithm
- Golden proportion number
- Two to the N minus one binary
- HASH_INCREMENT Indicates the binary of HASH_INCREMENT
- Hash conflict resolution
- Memory clean
-
- Exploratory cleaning
- Heuristic cleaning
- 4 and disadvantages
-
- Memory leak problem
- Parent threads cannot pass thread copy data
- 5. Summary of problems
- 6, actual combat application
-
- A complex scenario
- Application, for example,
- 7, reference
1. Basic concepts
Stack & Heap
Stack and Heap are the Stack and Heap we often say, here not to repeat, just need to note a little knowledge can, Stack is the thread private exclusive and thread-safe, Heap is all threads shared non-thread-safe. TheadLocal is designed to allow threads to have their own internal variables, and each thread is isolated from each other to achieve thread safety.
ThreadLocal
- ThreadLocal is what we call a thread-local variable. As shown in the figure above, it encapsulates a very important data structure, ThreadLocalMap, to provide the actual fetching, storing, and removing of thread variable data. We can think of ThreadLocal as a wrapper class or a mediation object. All core methods like GET, set, remove, and so on are provided and interacted with through ThreadLocal. The real brains behind the scenes are ThreadLocalMap, the inner class that encapsulates the final method logic and the internal data structure.
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode(a) {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
Copy the code
- ThreadLocal also holds an instance variable, threadLocalHashCode, which is a hash value used to calculate Entry hash table routes in ThreadLocalMap. ThreadLocalHashCode is generated by adding HASH_INCREMENT from the HASH_INCREMENT magic number, which will be discussed later in this section.
ThreadLocalMap
static class ThreadLocalMap {
// Entry in hash Map inherits WeakReference and points to threadLocal objects
// If the key is null, the entry is no longer needed and will be cleared from the table
// Such entries 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; }}private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
private int size = 0;
private int threshold; // Default to 0
private void setThreshold(int len) {
threshold = len * 2 / 3; } 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
- ThreadLocalMap is a custom hash map that holds thread local variables
- Its operations are confined to the ThreadLocal class and are not exposed
- This class is used on the private variables threadLocals and inheritableThreadLocals of the Thread class
- WeakReferences are used as the key type for hash table entries in order to hold a large number of long-lived threadLocal instances
- If the hash table runs out of space, entries with null keys will be cleared
Entry
Entry here inherits WeakReference class. Its internal structure is a key-value pair structure. Key is the referant of WeakReference, which is inherited from WeakReference object, and Value is the thread variable object data that is actually stored. <K,V>=<Referant,Object>, where Entry is generically restricted and finally defined as Entry<ThrealLocal,Object> data format
2, source code core method analysis
The get () the source code
public T get(a) {
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;
returnresult; }}return setInitialValue();
}
Copy the code
- Gets the ThreadLocalMap inside the current thread
- If map exists, get the value of the current ThreadLocal
- If the map does not exist or the value cannot be found, setInitialValue is called to initialize the map
SetInitialValue () the source code
private T setInitialValue(a) {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null)
map.set(this, value);
else
createMap(t, value);
return value;
}
Copy the code
- Call the initialValue method to get the initialValue.
- Gets the ThreadLocalMap inside the current thread
- If the map exists, add the current ThreadLocal and value to the map
- If the map does not exist, create a ThreadLocalMap and store it inside the current thread
Sequence diagram of get() method
Set () the source code
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null)
map.set(this, value);
else
createMap(t, value);
}
Copy the code
- Gets the ThreadLocalMap inside the current thread
- If the map exists, add the current ThreadLocal and value to the map
- If the map does not exist, create a ThreadLocalMap and store it inside the current thread
Remove () the source code
public void remove(a) {
ThreadLocalMap m = getMap(Thread.currentThread());
if(m ! =null)
m.remove(this);
}
Copy the code
Remove () method sequence diagram
3. Core algorithm
Hash generating algorithm
Hash generating algorithm: HASH_INCREMENT is a hexadecimal hash magic number that participates in the hash calculation. In this case, 0x61C88647 is the hexadecimal hash magic number. When converted to hexadecimal, -1640531527 is generated by incrementing HASH_INCREMENT each time
Index generation algorithm
Key. ThreadLocalHashCode & (Len-1)
- Key. threadLocalHashCode is the hash value obtained by incrementing HASH_INCREMENT by 1
- Len is the current capacity of the hash slot. The default value for initialization is 16
- HASH_INCREMENT HASH_INCREMENT = (2 ^ N ^ -1)
Golden proportion number
ThreadLocalMap uses the golden number approach, which greatly reduces hash collisions.
- Golden section number: The golden section number of int is calculated by multiplying the maximum value of int by math.sqrt (5) -1) / 2. The golden section number of int is -1640531527.
- Hash magic number: The hash number is changed by HASH_INCREMENT itself. After research, it is found that this will have better hash performance.
- High and low bits: (2 N power -1) the generated numbers have a feature, the high binary is 0, the low binary is 1
- Efficient bitwise and: HASH_INCREMENT increment HASH_INCREMENT increment HASH_INCREMENT increment increment HASH_INCREMENT increment increment HASH_INCREMENT increment increment increment HASH_INCREMENT increment increment increment HASH_INCREMENT increment increment increment increment So it becomes HashCode & (2 to the NTH -1) = HashCode % (2 to the NTH -1), but bitwise and is more efficient than modulo
Two to the N minus one binary
2the1Power of num:1 binary:00000000000000000000000000000001
2the2Power of num:3 binary:00000000000000000000000000000011
2the3Power of num:7 binary:00000000000000000000000000000111
2the4Power of num:15 binary:00000000000000000000000000001111
2the5Power of num:31 binary:00000000000000000000000000011111
2the6Power of num:63 binary:00000000000000000000000000111111
2the7Power of num:127 binary:00000000000000000000000001111111
2the8Power of num:255 binary:00000000000000000000000011111111
2the9Power of num:511 binary:00000000000000000000000111111111
2the10Power of num:1023 binary:00000000000000000000001111111111
2the11Power of num:2047 binary:00000000000000000000011111111111
2the12Power of num:4095 binary:00000000000000000000111111111111
2the13Power of num:8191 binary:00000000000000000001111111111111
2the14Power of num:16383 binary:00000000000000000011111111111111
2the15Power of num:32767 binary:00000000000000000111111111111111
2the16Power of num:65535 binary:00000000000000001111111111111111
2the17Power of num:131071 binary:00000000000000011111111111111111
2the18Power of num:262143 binary:00000000000000111111111111111111
2the19Power of num:524287 binary:00000000000001111111111111111111
2the20Power of num:1048575 binary:00000000000011111111111111111111
2the21Power of num:2097151 binary:00000000000111111111111111111111
2the22Power of num:4194303 binary:00000000001111111111111111111111
2the23Power of num:8388607 binary:00000000011111111111111111111111
2the24Power of num:16777215 binary:00000000111111111111111111111111
2the25Power of num:33554431 binary:00000001111111111111111111111111
2the26Power of num:67108863 binary:00000011111111111111111111111111
2the27Power of num:134217727 binary:00000111111111111111111111111111
2the28Power of num:268435455 binary:00001111111111111111111111111111
2the29Power of num:536870911 binary:00011111111111111111111111111111
2the30Power of num:1073741823 binary:00111111111111111111111111111111
Copy the code
It’s not hard to see that the binary of numbers that are powers of two to the minus one has the characteristic that the highest order is all zeros, and the lowest order is all ones.
HASH_INCREMENT Indicates the binary of HASH_INCREMENT
id:1 hashCode:1640531527 binary:01100001110010001000011001000111
id:2 hashCode:-1013904242 binary:11000011100100010000110010001110
id:3 hashCode:626627285 binary:00100101010110011001001011010101
id:4 hashCode:-2027808484 binary:10000111001000100001100100011100
id:5 hashCode:-387276957 binary:11101000111010101001111101100011
id:6 hashCode:1253254570 binary:01001010101100110010010110101010
id:7 hashCode:-1401181199 binary:10101100011110111010101111110001
id:8 hashCode:239350328 binary:00001110010001000011001000111000
id:9 hashCode:1879881855 binary:01110000000011001011100001111111
id:10 hashCode:-774553914 binary:11010001110101010011111011000110
id:11 hashCode:865977613 binary:00110011100111011100010100001101
id:12 hashCode:-1788458156 binary:10010101011001100100101101010100
id:13 hashCode:-147926629 binary:11110111001011101101000110011011
id:14 hashCode:1492604898 binary:01011000111101110101011111100010
id:15 hashCode:-1161830871 binary:10111010101111111101111000101001
id:16 hashCode:478700656 binary:00011100100010000110010001110000
id:17 hashCode:2119232183 binary:01111110010100001110101010110111
id:18 hashCode:-535203586 binary:11100000000110010111000011111110
id:19 hashCode:1105327941 binary:01000001111000011111011101000101
id:20 hashCode:-1549107828 binary:10100011101010100111110110001100
id:21 hashCode:91423699 binary:00000101011100110000001111010011
id:22 hashCode:1731955226 binary:01100111001110111000101000011010
id:23 hashCode:-922480543 binary:11001001000001000001000001100001
id:24 hashCode:718050984 binary:00101010110011001001011010101000
id:25 hashCode:-1936384785 binary:10001100100101010001110011101111
id:26 hashCode:-295853258 binary:11101110010111011010001100110110
id:27 hashCode:1344678269 binary:01010000001001100010100101111101
id:28 hashCode:-1309757500 binary:10110001111011101010111111000100
id:29 hashCode:330774027 binary:00010011101101110011011000001011
id:30 hashCode:1971305554 binary:01110101011111111011110001010010
id:31 hashCode:-683130215 binary:11010111010010000100001010011001
Copy the code
The above hashCode is generated 32 times by nexthashcode.getandAdd (HASH_INCREMENT) Since the binary high order of (2 ^ N -1) is all 1 and ampersand, the more hashCode has hash property, the final index value will also have good hash property and the possibility of hash collisions will be reduced. In the case that the binary of (2 ^ N -1) is fixed, that is, the low bits are all 1 and the high bits are all 0. Therefore, if the high and low bits of hashCode are too much the same, serious collisions will occur. Only when the high and low bits of hashCode have very good differences, as shown in the figure above, can collisions be reduced
id:1 hashCode:1640531527 index:7
id:2 hashCode:-1013904242 index:14
id:3 hashCode:626627285 index:21
id:4 hashCode:-2027808484 index:28
id:5 hashCode:-387276957 index:3
id:6 hashCode:1253254570 index:10
id:7 hashCode:-1401181199 index:17
id:8 hashCode:239350328 index:24
id:9 hashCode:1879881855 index:31
id:10 hashCode:-774553914 index:6
id:11 hashCode:865977613 index:13
id:12 hashCode:-1788458156 index:20
id:13 hashCode:-147926629 index:27
id:14 hashCode:1492604898 index:2
id:15 hashCode:-1161830871 index:9
id:16 hashCode:478700656 index:16
id:17 hashCode:2119232183 index:23
id:18 hashCode:-535203586 index:30
id:19 hashCode:1105327941 index:5
id:20 hashCode:-1549107828 index:12
id:21 hashCode:91423699 index:19
id:22 hashCode:1731955226 index:26
id:23 hashCode:-922480543 index:1
id:24 hashCode:718050984 index:8
id:25 hashCode:-1936384785 index:15
id:26 hashCode:-295853258 index:22
id:27 hashCode:1344678269 index:29
id:28 hashCode:-1309757500 index:4
id:29 hashCode:330774027 index:11
id:30 hashCode:1971305554 index:18
id:31 hashCode:-683130215 index:25
id:32 hashCode:957401312 index:0
Copy the code
The above is the case where the index values are generated 32 times when the length is 32. It is found that the index values are evenly distributed and there is no collision.
Hash conflict resolution
When there’s a hash conflict, it does it to see if it’s the same object or if it can be replaced, otherwise it moves back one bit, and it’s addressing again.
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
Copy the code
Memory clean
The reason ThreadLocal uses weak references for Entry keys is to reclaim as soon as possible to avoid taking up a large amount of memory space. In addition, it creatively adds two methods of exploratory clearing and heuristic clearing to reclaim and clear memory objects when core methods such as GET and SET are called
Exploratory cleaning
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// Because the entry corresponding ThreadLocal has been reclaimed, value is set to null, explicitly disconnecting strong references
tab[staleSlot].value = null;
// Explicitly set this entry to NULL for garbage collection
tab[staleSlot] = null;
size--;
Entry e;
int i;
for(i = nextIndex(staleSlot, len); (e = tab[i]) ! =null; i = nextIndex(i, len)) { ThreadLocal<? > k = e.get();// Clears the entries corresponding to ThreadLocal that have been reclaimed
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
/* * Rehash is required for cases that have not yet been recycled. * * If the index h moulded from Len by the ID of ThreadLocal is not the current slot I, * will linearly detect the first empty slot from H and move the current entry forward. * /
int h = k.threadLocalHashCode & (len - 1);
if(h ! = i) { tab[i] =null;
/* * There is a comment in the original code that is worth noting. * * Unlike Knuth 6.4 Algorithm R, we must scan until * null because multiple entries could have been stale. * * This passage refers to the R algorithm in Chapter 6.4 (Hashing) * of Knuth Gartner's book TAOCP (The Art of Computer Programming). The R algorithm describes how to remove an element from a hash table using linear probes. * R algorithm maintains an index of the last deleted element. When the index * of an entry whose hash value is modulo is not traversed in a non-empty continuous segment, it moves the entry to the position of index and updates the current position to the new index. * Continues to scan backward until an empty entry is encountered. * * ThreadLocalMap uses weak references, so each slot has three states: valid (value not reclaimed), invalid (value reclaimed), and empty (entry==null). * It is precisely because ThreadLocalMap entries have three states that the R algorithm of Gartner's original book cannot be completely applied. * * Since the expungeStaleEntry function also cleans up invalid slots during scanning and turns them into empty slots, * If R is applied directly, entries with the same hash value may be broken (empty entries in the middle). * /
while(tab[h] ! =null) { h = nextIndex(h, len); } tab[h] = e; }}}// Returns the first empty slot index after staleSlot
return i;
}
Copy the code
- Modified gartner’s R algorithm for removing an element from a hash table using linear probes according to the scenario
- If the slot corresponding to index is the threadLocal to read, the result is returned
- Call getEntryAfterMiss linear probe, call expungeStaleEntry for segment cleaning every invalid slot encountered during the process; If the key is found, the result entry is returned
- No key found, return null
Heuristic cleaning
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
// I itself is not an invalid slot in any case, so start with the next one
i = nextIndex(i, len);
Entry e = tab[i];
if(e ! =null && e.get() == null) {
// Expand the scan control factor
n = len;
removed = true;
// Clear a segmenti = expungeStaleEntry(i); }}while ((n >>>= 1) != 0);
return removed;
}
Copy the code
- The slot is cleared heuristically. The entry corresponding to I is non-invalid (the pointed ThreadLocal is not reclaimed or the entry itself is empty), and n is used to control the number of scans
- Normally, if no invalid slot is found in log n scans, the function ends
- If an invalid slot is found, set n to len of the table, clear consecutive segments, and continue scanning from the next empty slot
- This function is called when an insert is being inserted and when an invalid slot is being replaced. This function is called when an insert is being inserted and when an invalid slot is being replaced
4 and disadvantages
Memory leak problem
To collate object references, use **++>Indicates a strong reference– >** indicates a weak reference
- Thread ++> ThrealLocal.ThreadLocalMap ++> Entry ++> key (referant) –> ThreadLocal
- Thread + + > ThrealLocal. ThreadLocalMap + + > Entry value + + + + > > Object as key (referant) — – > ThreadLocal is weak references, gc will recycle the key, In this case, the key is null, but the Thread, as the Root of the chain of references, does not disappear immediately after the Thread completes execution. Instead, it resides in the Stack. In this case, there are generally two ways: one is executed in a for loop, and the other is executed in a Thread pool. Therefore, strongly referenced values will not be freed, resulting in memory overflow
Parent threads cannot pass thread copy data
Using a ThreadLocal in the main thread cannot be passed directly to child threads, requiring variable substitution via thread closure.
5. Summary of problems
- If the key of a ThreadLocal is a weak reference, will the key be null after GC in threadlocale.get ()? Because the key of Entry
,object>
is a WeakReference, it will be reclaimed after GC
- ThreadLocalMap data structure in ThreadLocal? Hash table
- Hash algorithm for ThreadLocalMap? Key.threadlocalhashcode & (length-1), length is a power of 2
- How to resolve Hash conflicts in ThreadLocalMap? Open addresses, secondary addressing, because of the use of golden section number hash, hashing is very good, the likelihood of collisions is very low, so there is no chain address resolution conflict like HashMap
- ThreadLocalMap Capacity expansion mechanism?
length2/3 triggers the rehash logic for exploratory cleaning, and finally determines size >= threshold3/4 to determine whether to actually call resize - How to clean expired keys in ThreadLocalMap? Exploratory and heuristic clean-up processes? ExpungeStaleEntry ()) and heuristic (cleanSomeSlots()) probe cleanup is based on the current Entry and ends when the value is null, which belongs to linear probe cleanup
- Threadlocalmap.set () slightly
- Threadlocalmap.get () ¶ slightly
- How is ThreadLocal used in your project? Potholes? Parent threads cannot pass thread variables, and using a thread pool in the main thread is equivalent to using child threads in the parent thread that cannot pass values
6, actual combat application
A complex scenario
Spring container, RPC full-link traceId transmission, etc
Application, for example,
In a single application, we do not declare multiple ThreadLocal’s, that is, we do not allow threads to hold multiple keys of ThreadLocalMap. Since ThreadLocal is generic, we can pass in a thread-safe container. Let ThreadLocalMap held by Thread have only one key, that is, only one key reference of ThreadLocal, and the stored Object can be a Map or List. We can operate the container according to the business scenario, which can be satisfied in most cases. ThreadLocal is a private final static ThreadLocal. If ThreadLocal is a private final static ThreadLocal is a private final static ThreadLocal.
/ * * *@author: guanjian
* @date: 2020/07/08 now *@description: Environment variable */
@Component("contextHolder")
public class ContextHolder<T.R> {
private final static Logger LOGGER = LoggerFactory.getLogger(ContextHolder.class);
/** * The entry object */
public final static String REQUEST_PARAM = "request_param";
/** * object */
public final static String RESPONSE_PARAM = "response_param";
/** ** pass the value object */
public final static String TRANSMIT_PARAM = "transmit_param";
/** * thread variable */
private final static ThreadLocal<Map<Object, Object>> localVariable = ThreadLocal.withInitial(() -> Maps.newHashMap());
public void bindLocal(Object key, Object value) {
Objects.requireNonNull(key, "key can not be null");
Map holder = localVariable.get();
holder.put(key, value);
localVariable.set(holder);
LOGGER.debug("[ContextHolder] key={},value={} binded.", key, JSON.toJSONString(value));
}
public Object getLocal(Object key) {
if (CollectionUtils.isEmpty(localVariable.get())) return null;
Object value = localVariable.get().get(key);
LOGGER.debug("[ContextHolder] key={},value={} getted.", key, JSON.toJSONString(value));
return value;
}
public void bindRequest(T value) {
bindLocal(REQUEST_PARAM, value);
}
public T getRequest(a) {
return (T) localVariable.get().get(REQUEST_PARAM);
}
public void bindResponse(R value) {
bindLocal(RESPONSE_PARAM, value);
}
public R getResponse(a) {
return (R) localVariable.get().get(RESPONSE_PARAM);
}
public void bindTransmit(Object value) {
bindLocal(TRANSMIT_PARAM, value);
}
public Object getTransmit(a) {
return getLocal(TRANSMIT_PARAM);
}
public void clear(a) { localVariable.remove(); }}Copy the code
7, reference
www.cnblogs.com/wang-meng/p… https://blog.csdn.net/zjcsuct/article/details/104310194 www.iocoder.cn/JDK/ThreadL… https://blog.csdn.net/qq_22167989/article/details/89448670