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

    is a WeakReference, it will be reclaimed after GC
    ,object>
  • 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