Take a quick look at the ThreadLocal source code

Commonly used method

 ThreadLocal<String> threadLocal = new ThreadLocal<>();
 threadLocal.set("aa");
 threadLocal.get();
 threadLocal.remove();
Copy the code

ThreadLocal data structure

Thread class a type for ThreadLocal. ThreadLocalMap threadLocals instance variables, that is to say, each Thread has an own ThreadLocalMap.

A ThreadLocalMap has its own separate implementation and can simply treat its key as a ThreadLocal and its value as a value put into the code (the key is not actually a ThreadLocal itself, but a weak reference to it).

When a thread puts a value into a ThreadLocal, it stores the value into its own ThreadLocalMap, and reads the value using ThreadLocal as a reference to find the corresponding key in its own map, thus achieving thread isolation.

ThreadLocalMap has a similar structure to HashMap, except that HashMap is implemented by array + list, whereas ThreadLocalMap has no list structure and uses linear detection for Hash collisions. We also notice Entry, whose key is ThreadLocal
k, inheriting from WeakReference, which is also known as WeakReference type.

  • Strong references: We often create objects that are strong references. As long as strong references exist, the garbage collector will never reclaim the referenced object, even when memory is low
  • SoftReference: an object with a SoftReference modifier is called a SoftReference. The object to which a SoftReference points is reclaimed when its memory is about to overflow
  • Weak references: objects decorated with WeakReference are called weak references. Whenever garbage collection occurs, if the object is only pointed to by weak references, it will be collected
  • Virtual References: Virtual references are the weakest references and are defined in Java using PhantomReference. The only purpose of a virtual reference is to queue up notifications that an object is about to die

Memory leak problem

If our ThreadLocal object has no strong reference, the weakly referenced key is reclaimed, but the value is not. If the thread does not exit, the value will persist, which will cause a memory leak. Since a Thread has one ThreadLocalMap, the lifetime of a ThreadLocalMap is the same as that of a Thread.

Set method

The set() call determines whether the current Thread has a ThreadLocalMap of the member variables of the Thread class. 1. If map == null, the ThreadLocalMap of a Thread class will be initialized, and the index in the array will be calculated by using the key of ThreadLocal. Since it is the first initialization, there is no hash conflict, so it will be inserted directly. 2.map ! = null, get ThreadLocalMap directly, call the Map’s set() method, execute the related logic. The set() method of ThreadLcoalMap computes the Hash to get the array’s index.

  1. indexNode is empty, directly new Entry(key,value), andinsert.
  2. indexThe node is not empty, a hash conflict occurs. backwardTraverse ` ` nextIndex ()Search for a new Entry(key,value) until the index node is not nullInsert table [I]. The value of I changes, and the following happens in order during the search
    • If the key values are the same, the ThreadLocal object directly updates the value
    • If key==null, the reference may expirereplaceStaleEntry()Method to replace expired data. Iterates forward with the current node, marking the expiration positionstaleSlot.
  3. It is executed after insertionCleanSomeSlots cleaningMethod, and returns whether expansion is required. The load factor is also 0.75. Double the capacity.

Set() if ThreadLocalMap does not exist

Let’s take a look at the flow when we first set using ThreadLocal in Thread.

ThreadLocal. In class classpublic void set(T value) {
       // Get the current thread
        Thread t = Thread.currentThread();
       // Get the current map by thread
        ThreadLocalMap map = getMap(t);
        if(map ! =null)
            map.set(this, value);
        else
            // Initialize one if it does not exist
            createMap(t, value);
    }


void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue); } ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) { table =new Entry[INITIAL_CAPACITY];
           // Hash the array subscript
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            // Key is a weak reference
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
 }
Copy the code

How to resolve Hash conflicts in ThreadLocalMap?

Initialize the Map method by simply looking at the set() method. ThreadLocalMap is a hash stored Map. Since it’s a hash, there must be steps to resolve hash conflicts.

// Computes the hash to get the array subscript value
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);


public class ThreadLocal<T> {
    private final int threadLocalHashCode = nextHashCode();

    private static AtomicInteger nextHashCode = new AtomicInteger();

    private static final int HASH_INCREMENT = 0x61c88647;

    private static int nextHashCode(a) {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    
    static class ThreadLocalMap { 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

As you can see from the code, our key is threadLocalHashCode. Each time we create a new ThreadLocal object, its threadLocalHashCode increments HASH_INCREMENT = 0x61C88647. This value is very special. It is the Fibonacci number, also known as the Golden ratio number. If the hash increment is this number, the advantage is that the hash distribution is very uniform. If you’re interested, look up Fibonacci algorithms.

The solution** We simulate inserting a value of value=27, hash result index=4, but index=4 already exists, so we iterate backwards to find the node with null index=8, find no value, then insert the corresponding value. This is the classic hash conflict resolution algorithm,Linear detection method.

Set() if ThreadLocalMap exists

Going back to our set method, what happens when we first initialize a ThreadLocalMap and then perform a set() operation? By observing the source code main logic in

ThreadLocalMap.java

private void set(ThreadLocal
        key, Object value) {

            Entry[] tab = table;
            int len = tab.length;
            // Evaluates the hash to get the array index
            int i = key.threadLocalHashCode & (len-1);
			// Exit the loop from the current index iteration array table[I]==null
            for(Entry e = tab[i]; e ! =null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<? > k = e.get();// The key is equal, representing the same threadLocal object, directly updating the value
                if (k == key) {
                    e.value = value;
                    return;
                }
				// Node key==null, indicating that an expired node is detected
                if (k == null) {
                	// staleSlot is the location of the expired node
                    replaceStaleEntry(key, value, i);
                    return; }}// Insert or overwrite a node
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if(! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }Copy the code

For loop logic:

  1. Traverse the currentkeyValue in the bucketEntryThe data is empty, which means there’s no data collision in the hash array. Pop outforLoop, directsetData is stored in the corresponding bucket
  2. ifkeyValue in the bucketEntryData is not null
    1. k = key, indicating that the currentThe set operation is a replacement operation that does the replacement logic and returns directly
    2. key = null, indicating the current bucket locationEntryIf yes, go toreplaceStaleEntry()Method (core method), and return
  3. forAfter the loop is executed, continuing to execute indicates that the process of backward iteration is encounteredentryfornullIn the case
    1. EntryfornullCreate a new one in the bucketEntryobject
    2. line++sizeoperation
  4. callcleanSomeSlots()Do a heuristic cleanup to clean up the hash arrayEntrythekeyExpired data
    1. If no data is cleared after the cleaning is completed, andsizeIf the threshold (2/3 of the array length) is exceeded, proceedrehash()operation
    2. rehash()A round of exploratory cleaning will be carried out to clear the expiration dateKey ‘, if after cleaning is completedsize >= threshold – threshold / 4, the actual expansion logic will be performed (expansion logic goes back)

The get method

Get method is relatively simple, the general logic is:

Gets the ThreadLocalMap object for Thread. Map == null, execute the set initializer method. Map ! = null, the getEntry method is executed. GetEntry: Computes the index value based on the hash. If the keys of table[I] are equal, the value is returned. If table[I]==null is not equal, hash conflicts are generated before and the search is completed until table[I]==null or value is returned after the search. If an expired node whose table[I]. Key is null is found during traversal, probe data is reclaimed.

We take theget(ThreadLocal1)For example, passhashAfter calculation, correctslotThe position should be 4, andindex=4Data has been generated for the slot andkeyValue is not equal toThreadLocal1, so we need to continue to iterate on the search. Iteration toindex=5Data at this timeEntry.key=null, triggers a probe data reclamation operation, and executesexpungeStaleEntry()Method, after execution,The index 5, 8Is recycled, andThe index of 6, 7The data will move forward, and then continue to iterate toindex = 6That’s when I found itkeyThe values are equalEntryData, as shown in the figure below:

Reference: www.jianshu.com/p/134d72d37…