@[TOC]

introduce


ThreadLocal,作者:Josh Bloch and Doug Lea

  • ThreadLocal raises the local variables of a thread, which has its own local variables for accessing a thread.
  • When using ThreadLocal to maintain variables, ThreadLocal provides a separate copy of the variable for each thread that uses the variable, so each thread can independently change its own copy without affecting the corresponding copy of other threads.
static final ThreadLocal<T> sThreadLocal = new ThreadLocal<T>();

sThreadLocal.set()

sThreadLocal.get()
Copy the code

The source code implementation of ThreadLocal covers data structures, zip storage, Fibonacci sequences, the magic 0x61C88647, weak Reference, stale key detection cleanup, and more

Application scenarios

Local variable case

public class test {

    public static void main(String[] args) {
        Res res=new Res();
        Thread thread = new Thread(res,Thread 1 "");
        Thread thread2 = new Thread(res,Thread 2 ""); thread.start(); thread2.start(); }}class Res implements Runnable {
    public  static Integer count = 0;

    public  Integer getNumber(a){
        return count++;
    }

    @Override
    public void run(a) {
        for (int i = 0; i < 3 ; i++) {
            System.out.println(Thread.currentThread().getName()+ ","+getNumber()); }}}Copy the code

  • It turns out that thread 1 and thread 2 share thread variables in a multi-threaded situation, and we need one thread to get the private variable
public class test {

    public static void main(String[] args) {
        Res res=new Res();
        Thread thread = new Thread(res,Thread 1 "");
        Thread thread2 = new Thread(res,Thread 2 ""); thread.start(); thread2.start(); }}class Res implements Runnable {
    / / create a ThreadLocal
    public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue(a) {
            return 0; }};public Integer getNumber(a) {
        int count = threadLocal.get() + 1;
        threadLocal.set(count);
        return count;
    }

    @Override
    public void run(a) {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + ","+ getNumber()); }}}Copy the code

  • Each thread has its own private variable, or local variable
  • ThreadLocal objects can provide thread-local variables. Each Thread has a copy of its own variable.

The data structure

        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(); ... }Copy the code

ThreadLocal’s array data storage structure is as follows:

  1. It is an array structure Entry[] whose key is ThreadLocal<? > k, inheriting from WeakReference, which is also known as WeakReference type

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

  3. 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.

  4. ThreadLocalMap has a similar structure to a HashMap, except that HashMap is implemented by an array + list, whereas ThreadLocalMap does not have a list structure.

Why does ThreadLocal use weak references?

Weak reference explanation:

Objects with only weak references have a shorter lifetime. When the garbage collector thread scans the memory area under its control, once it finds an object with only weak references, it reclaims its memory regardless of whether the current memory space is sufficient. However, because the garbage collector is a low-priority thread, objects that have only weak references are not necessarily found quickly.

Note: WeakReference reference itself is a strong reference, its internal (T reference) is the real WeakReference field, WeakReference is a container with WeakReference.

Why weak references:

Thread – > ThreadLocal. ThreadLocalMap – > Entry [] – > Enrty – > key (ThreadLocal object) and the value

The link is full of strong references. When the current thread is not finished, it holds all strong references, including recursively strong references, which are not collected by the garbage collector. When the reference of a ThreadLocal object is set to null, there are no strong references to the ThreadLocal instance in memory. The key for threadLocals is a weak reference to it, so it will be collected by GC. Next time, we can tell that the Entry object should be cleared by the fact that the Entry is not null and the key is null.

Why is key designed to be a weak reference

If each key has a strong reference to an object that points to a ThreadLocal, that is, a strong reference, then the ThreadLocal object cannot be collected by the GC because of its strong reference to an Entry object, causing a memory leak, unless the thread terminates and the map is reclaimed. When a reference to a ThreadLocal object is null, there are no strong references to the in-memory instance of ThreadLocal, and the Key of threadLocals is a weak reference to it, so it will be reclaimed by GC. Next time, we can tell that the Entry object should be cleared by the fact that the Entry is not null and the key is null.

The Hash algorithm

ThreadLocalMap implements its own hash algorithm to resolve hash array collisions.

	int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
Copy the code

	private static final int HASH_INCREMENT = 0x61c88647;


	private static AtomicInteger nextHashCode =
        new AtomicInteger();


	private final int threadLocalHashCode = nextHashCode();



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

Each time a ThreadLocal object is created, the threadLocal. nextHashCode value increases by 0x61C88647

This value, this is the Fibonacci number or the golden line. To make data more hashed, reduce hash collisions

Why use 0x61C88647

  • If you’ve done math, the golden ratio is square root of 5 minus 1 over 2, which is 0.6180339887.
  • Then use 2 ^ 32 * 0.6180339887 to get: -1640531527, which is hexadecimal, 0x61C88647. So that’s where this number came from
public class test {

    private static final int HASH_INCREMENT = 0x61c88647;

    public static void main(String[] args) {
        test_idx();
    }
    
    static public void test_idx(a) {
        int hashCode = 0;
        for (int i = 0; i < 16; i++) {
            hashCode = i * HASH_INCREMENT + HASH_INCREMENT;
            int idx = hashCode & 15;
            System.out.println("Fibonacci hash:" + idx + "Ordinary hash:" + (String.valueOf(i).hashCode() & 15)); }}}Copy the code

Fibonacci hashes are very uniform and normal hashes up to 15 have been developed to produce collisions. This is the beauty of Fibonacci hashes, which reduce collisions so that the data store is more distributed and the time to get the data is roughly O(1).


Hash conflict

  1. Although ThreadLocalMap uses the golden ratio number as the hash factor, which greatly reduces the probability of hash collisions, conflicts still occur.
  2. The way to resolve conflicts in a HashMap is to create a list of objects in the array. If the list length exceeds a certain number, it will be converted to a red-black tree.
  3. ThreadLocal adopts the method of linear detection. The so-called linear detection is to determine the position of elements in the table array according to the hashcode value of the initial key. If it is found that elements with other key values have been occupied in this position, the fixed algorithm is used to find the next position of a certain step length and judge successively. Until we find a place to store it.
private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }
Copy the code
private static int prevIndex(int i, int len) {
            return ((i - 1> =0)? i -1 : len - 1);
        }
Copy the code

The source code interpretation

ThreadLocalMap

  • The nested inner class ThreadLocalMap is essentially a map, similar to implementations like HashMap, in the form of key-value. There is an inner class Entry, where key can be seen as an instance of ThreadLocal. But the essence is to hold a weak reference to a ThreadLocal instance
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; }}/** * 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 0
        /** * Increment i modulo len. */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0); } 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

Set add element

ublic void set(T value) {
	// Get the current thread
    Thread t = Thread.currentThread();
    Note: Key is the current thread
    ThreadLocalMap map = getMap(t);
    // map does not equal null call ThreadLocalMap set(this, value);
    if(map ! =null)
        map.set(this, value);
    else
    	// Create ThreadLocalMap in the current thread
        createMap(t, value);
}




void createMap(Thread t, T firstValue) {
    t.threadLocals = new `ThreadLocalMap`(this, firstValue);
}
Copy the code
private void set(ThreadLocal
        key, Object value) {
			Static Class Entry extends WeakReference
      
        >,
      >
			// So that in the absence of external strong references, GC occurs and the key is deleted.
            Entry[] tab = table;
            int len = tab.length;
            // Computes the array subscript hash algorithm
            int i = key.threadLocalHashCode & (len-1);
			 // The for loop quickly finds the insertion position
            for(Entry e = tab[i]; e ! =null;
                 // Find the value backwardse = tab[i = nextIndex(i, len)]) { ThreadLocal<? > k = e.get();// If k already exists, overwrite it
				if (k == key) {
                    e.value = value;
                    return;
                }
				// If it is null
                if (k == null) {
                	// How to replace expired data ----
                	replaceStaleEntry(key, value, i);
                    return; }}/ / set the value
            tab[i] = new Entry(key, value);
            int sz = ++size;
            // cleanSomeSlots removes old entries (key == null) heuristic cleaning)
            // If old entries are not cleared and the array is larger than the threshold, rehash
            if(! cleanSomeSlots(i, sz) && sz >= threshold)// Expansion method ------
                rehash();
        }
Copy the code

ReplaceStaleEntry replaces the element


// When the set operation is executed, the corresponding key is obtained and the expired entry is replaced
private void replaceStaleEntry(ThreadLocal<? > key, Object value,int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

			// slotToExpunge indicates the start index of the exploratory clearing of expired data. The default value is staleSlot
    		// Start with the current staleSlot and iterate forward to find data that has not expired
    		// For that will not end until Entry is null. If the expired data is found ahead, the update probe clears the expired data with the starting subscript I, that is, slotToExpunge = I
            int slotToExpunge = staleSlot;
            // Traverse forward to find the first expired entity subscript
            for (inti = prevIndex(staleSlot, len); (e = tab[i]) ! =null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;

            
            // Start looking back from staleSlot, also hit bucket with null Entry result
            // If k == key is encountered in the iteration, this indicates that there is substitution logic, replacing the new data and swapping the current staleSlot position
            // If slotToExpunge == staleSlot, the expired data cannot be found either in the forward or backward query
            // Change the index index of the current loop to start probing cleanup, that is, slotToExpunge = I.
            // Finally call cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); Perform heuristic expiration data cleansing.
            for (inti = nextIndex(staleSlot, len); (e = tab[i]) ! =null; i = nextIndex(i, len)) { ThreadLocal<? > k = e.get();// If the key is found and the new data is replaced
                if (k == key) {
                    e.value = value;

                    tab[i] = tab[staleSlot];
                    tab[staleSlot] = e;

                    // Start expunge at preceding stale entry if it exists
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    // Clear expired data
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }

              	// If k! = key If you go down, key = null indicates that the current Entry traversed is an expired data
              	// slotToExpunge == staleSlot indicates that no expired Entry is found during the initial forward data search
              	// If the condition is correct, update slotToExpunge to the current location (this is provided that the precursor node scan does not find expired data)
                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

            // If k = key is not found, and Entry is null is encountered,
            // Add new data to the slot corresponding to table[staleSlot]
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

            // If there are other objects that have expired, they need to be cleaned up
            if(slotToExpunge ! = staleSlot)// Clear expired data
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }
Copy the code

Why swap

  • Let’s take a look at what would happen if we didn’t swap, after setting values and cleaning up expired objects

  • So if we set key=15,value=new2 again, f(15)=5, because last time index=5 was out of date, it was cleared out, so we can have data, we can store it right here

  • So there are two keys =15 in the entire array, which is not allowed, so you have to swap



ExpungeStaleEntry Probe clearing

  • ThreadLocalMap has two ways of cleaning up stale key data: exploratory and heuristic.
  • ExpungeStaleEntry exploratory cleanup, starting with the currently encountered GC elements and working backwards. Until YOU hit NULL

Rehash until we Encounter null.

private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // Clear the data in slot TAB [staleSlot]
            // Then set then set size--
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            // Iterate backwards with the staleSlot position
            for(i = nextIndex(staleSlot, len); (e = tab[i]) ! =null; i = nextIndex(i, len)) { ThreadLocal<? > k = e.get();// Empty the slot if key == null is out of date, then size--
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                	// If key! = null Indicates that the key does not expire and the subscript position of the current key is recalculated to determine whether the subscript position of the current slot is correct
                	// If not h! = I, it indicates that a hash conflict is generated. In this case, the newly calculated correct slot position is iterated forward
                	// Find the last entry location
                    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.-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - -- -- -- --* we must scan for null because multiple entries may be out of date * ThreadLocal uses weak references, i.e. multiple states (collected, not collected), so it cannot be safely implemented using R */
                        while(tab[h] ! =null) h = nextIndex(h, len); tab[h] = e; }}}return i;
        }
Copy the code

CleanSomeSlots heuristic cleaning

  • ThreadLocalMap has two ways of cleaning up stale key data: exploratory and heuristic.
  • The cleanSomeSlots heuristic scans cells for expired elements, which are garbage collected. This function is called when a new element is added or another obsolete element is removed. It performs logarithmic scans as a balance between no scans (fast but garbage preserved) and scans proportional to the number of elements, which will find all garbage but result in some inserts that take O (n) time.
private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            // do while loop keeps moving right to find expired elements that have been cleaned up
            // The end result is expungeStaleEntry
            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

Rehash Capacity expansion mechanism

  • At the end of the threadLocalmap.set () method, the rehash() logic is executed if no data has been cleaned up after the heuristic is complete and the number of entries in the current hash array has reached the list expansion threshold
if(! cleanSomeSlots(i, sz) && sz >= threshold) rehash();Copy the code

Rehash () implements:

private void rehash(a) {
	// Perform exploratory cleaning from the starting position of the table back to ------ as shown in ------
     expungeStaleEntries();

     // Some Entry data with null keys were cleared from the Kennel in the table
     // Size >= threshold-threshold / 4
     if (size >= threshold - threshold / 4)
          resize();
}


private void expungeStaleEntries(a) {
    Entry[] tab = table;
    int len = tab.length;
    // Clear from the start of the table
    for (int j = 0; j < len; j++) {
        Entry e = tab[j];
        if(e ! =null && e.get() == null) expungeStaleEntry(j); }}Copy the code

Resize () is implemented

        private void resize(a) {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            // The expanded size is oldLen * 2
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;
			// Then iterate over the old hash table
            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                // Current Entry is not null
                if(e ! =null) { ThreadLocal<? > k = e.get();// If key is null, set value to null to help GC garbage collection
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                    	If the key is not null, the hash position is recalculated
                        int h = k.threadLocalHashCode & (newLen - 1);
                        // Add it to a new TAB. If hash conflicts occur, search for the slot with the latest entry null
                        // After traversal, all entries in the oldTab have been placed in the new TAB. Recalculate the TAB expansion threshold
                        while(newTab[h] ! =null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

            setThreshold(newLen);
            size = count;
            table = newTab;
        }
Copy the code

Get gets elements

public T get(a) {
		// Get the current thread
        Thread t = Thread.currentThread();
        // Get whether the map has a value
        ThreadLocalMap map = getMap(t);
        // does not equal null
        if(map ! =null) {
        	/ / get the value
            ThreadLocalMap.Entry e = map.getEntry(this);
            // Return if not null
            if(e ! =null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                returnresult; }}// Set initialization
        return setInitialValue();
    }
Copy the code
	private Entry getEntry(ThreadLocal
        key) {
			// Locate the subscript in the array
	        int i = key.threadLocalHashCode & (table.length - 1);
	        Entry e = table[i];
	        // The corresponding entry exists and is valid, and the ThreadLocal that the weak reference points to is the key
	        if(e ! =null && e.get() == key)
	             return e;
	         else
	         	 // Since linear probe is used, it is possible to find the target Entry later.
	             return getEntryAfterMiss(key, i, e);
	        }




     private Entry getEntryAfterMiss(ThreadLocal<? > key,int i, Entry e) {
           Entry[] tab = table;
           int len = tab.length;
		   // Probe backward until empty entry is encountered based on linear probe method.
           while(e ! =null) { ThreadLocal<? > k = e.get();// Return directly to the target
                if (k == key)
                    return e;
                if (k == null)
                	// The ThreadLocal corresponding to this entry has been reclaimed. Call expungeStaleEntry to clean up invalid entries
                    expungeStaleEntry(i);
                else
                	// Circle meaning to go behind
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }
Copy the code

Remove Remove elements

private void remove(ThreadLocal
        key) {
            Entry[] tab = table;
            int len = tab.length;
            // Calculate the key subscript
            int i = key.threadLocalHashCode & (len-1);
            // Iterate over the clear element
            for(Entry e = tab[i]; e ! =null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    // Probe cleanup
                    expungeStaleEntry(i);
                    return; }}}Copy the code

ThreadLocal memory leaks

  1. ExpungestaleEntry () = expungestaleEntry(); expungestaleEntry() = expungestaleEntry(); However, if we do not call get and set methods, we may face this memory overflow, so we should develop the use of remove method when not in use to speed up garbage collection, avoid memory overflow
  2. In thread reuse, the lifetime of a thread is very long, and large objects are not recycled for a long time, which affects the efficiency and safety of system operation and leads to memory leakage

InheritableThreadLocal

  • When we use ThreadLocal, there is no way to share a thread copy created by the parent thread with the child thread in an asynchronous scenario
  • To solve this problem, there is also an InheritableThreadLocal class in the JDK

Testing:

public class a {

    public static void main(String[] args) {
        ThreadLocal<String> ThreadLocal = new ThreadLocal<>();
        ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
        ThreadLocal.set("Parent data :threadLocal");
        inheritableThreadLocal.set("Parent class data :inheritableThreadLocal");

        new Thread(new Runnable() {
            @Override
            public void run(a) {
                System.out.println("Child thread gets parent 'ThreadLocal' data:" + ThreadLocal.get());
                System.out.println(The child thread gets the inheritableThreadLocal data from the parent class:+ inheritableThreadLocal.get()); } }).start(); }}Copy the code

The child Thread creates the child Thread from the parent Thread by calling the new Thread() method. Thread#init is called in the Thread constructor. Copy parent thread data to child thread in init method:


Thread init() method source

if(inheritThreadLocals && parent.inheritableThreadLocals ! =null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
Copy the code

[InheritableThreadLocal] [init()] [Thread pool] [InheritableThreadLocal] [init()] [Thread pool]] [Thread pool] [init()]] [Thread pool] So there’s a problem here.

Of course, if there is any problem, there will be a solution. If there is any TransmittableThreadLocal, alibaba has opened an open source module to solve this problem.



Personal blog address: blog.yanxiaolu.cn /