A Brief Analysis of ThreadLocal Source Code by Ali Architect: Use of Golden Section Number

Keywords: Java Attribute jvm Programming

Links to the original text: https://www.jianshu.com/p/48eca67fb790

I. premise

A recently touched project is compatible with old and new systems, and ultimately uses ThreadLocal (actually Inheritable ThreadLocal) to obtain variables shared in parent threads in child threads. The problem was solved, but later found that the understanding of ThreadLocal was not deep enough, so by the way, I read the source code of ThreadLocal. Before we talk about ThreadLocal, buy a pass and talk about the golden section. This article uses JDK8(1.8.0_181) when reading ThreadLocal source code.

2. Golden Section Number and Fibonacci Sequence

First, review the Fibonacci sequence. The following derivation is from a search engine's wiki:

  • Fibonacci sequence: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,...
  • General Term Formula: Assuming that F(n) is the nth term of the sequence (n < N*), this sentence can be written in the following form: F(n) = F(n-1) + F(n-2).

It is interesting to note that such a sequence of totally natural numbers is expressed in terms of irrational numbers. And when n tends to infinity, the ratio of the former to the latter is closer and closer to 0.618 (or the fractional part of the ratio of the latter to the former is closer and closer to 0.618), which is called the golden section. The proof process is as follows:

The exact value of golden section is (root 5 - 1) / 2, which is about 0. 618.

Application of Golden Section Number

Golden Section is widely used in art, photography and other fields, because it has strict proportionality, artistry, harmony, contains rich aesthetic value, can stimulate people's aesthetic feeling. Of course, these are not the research directions of this paper. First, we try to find the specific values of the golden section of unsigned integers and signed integers.

public static void main(String[] args) throws Exception {
    //The 32 power of golden section number * 2 = 2654435769 - This is the value corresponding to the golden section number of unsigned 32-bit integers
	long c = (long) ((1L << 32) * (Math.sqrt(5) - 1) / 2);
	System.out.println(c);
    //Force conversion to a signed 32-bit integer with a value of - 1640531527
	int i = (int) c;
	System.out.println(i);
}

Understand through a line graph:

That is, 2654435769 is the golden section value of 32-bit unsigned integers, while - 1640531527 is the golden section value of 32-bit signed integers. The hash magic number in ThreadLocal is 1640531527 (hexadecimal is 0x61c88647). Why use 0x61c88647 as hash magic number? Let's talk about the hash rule of ThreadLocal in ThreadLocal Map (ThreadLocal exists in the form of Key):

Hash algorithm: keyIndex = ((i + 1) * HASH_INCREMENT) & (length - 1)

Among them, i is the number of ThreadLocal instances, where HASH_INCREMENT is the hash magic number 0x61c88647, and length is the number of Entry(K-V structure) that can be accommodated in ThreadLocal Map (or capacity). In ThreadLocal, the initial capacity of the inner class ThreadLocalMap is 16, and it is always the power of 2 after expansion, so we can write a Demo to simulate the whole hashing process.

public class Main {
	
	private static final int HASH_INCREMENT = 0x61c88647;

	public static void main(String[] args) throws Exception {
		hashCode(4);
		hashCode(16);
		hashCode(32);
	}

	private static void hashCode(int capacity) throws Exception {
		int keyIndex;
		for (int i = 0; i < capacity; i++) {
			keyIndex = ((i + 1) * HASH_INCREMENT) & (capacity - 1);
			System.out.print(keyIndex);
			System.out.print(" ");
		}
		System.out.println();
	}
}

In the example above, we simulated the ThreadLocalMap capacity of 4, 16, 32, without triggering expansion, and "put" 4, 16, 32 elements into the container respectively. The output results are as follows:

3 2 1 0 
7 14 5 12 3 10 1 8 15 6 13 4 11 2 9 0 
7 14 21 28 3 10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26 1 8 15 22 29 4 11 18 25 0

The elements of each group are just filled with the whole container after hashing algorithm, that is to say, perfect hashing is realized. In fact, this is not accidental, in fact, the whole hash algorithm can be converted into polynomial proof: proof (x - y)* HASH_INCREMENT!= 2 ^ n* (n m), in the case of x!= y, n!= m, HASH_INCREMENT is odd, the specific proof can be completed by itself. The API document with HASH_INCREMENT assignment of 0x61c88647 is annotated as follows:

The difference between consecutive generated hash codes (incremental values) converts the implicit sequential thread local id to the multiplicative hash value of almost optimal distribution, which eventually generates a power-2 hash table.

IV. What is ThreadLocal

The following quotes the API annotation of ThreadLocal:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID)

ThreadLocal provides thread local variables. These variables are different from normal variables because each thread has its own, independently initialized copy of the variables when accessing the ThreadLocal instance (through its get or set method). ThreadLocal instances are usually private static fields in classes that are used to associate state (for example, user ID or transaction ID) with threads.

ThreadLocal is written by two master authors in Java, Josh Bloch and Doug Lea. Josh Bloch is the founder of JDK5 Language Enhancement, Java Collection Framework and author of the Effective Java series. Doug Lea is the author of the JUC(java.util.concurrent) package and a master of Java concurrent programming. So the source code of ThreadLocal is worth learning.

V. Principles of ThreadLocal

ThreadLocal is called thread local variable, but in fact it does not store any information. It can be understood as a bridge between Thread and ThreadLocalMap to manipulate variables stored in ThreadLocalMap. It mainly provides several methods of initialization, set(), get(), remove(). This may be a bit abstract. Here's a diagram illustrating a simple flow chart of the set() and get() methods that use ThreadLocal instances in threads.

Assuming we have the following code, the thread name of the main thread is main (or maybe not main):

public class Main {
	
	private static final ThreadLocal<String> LOCAL = new ThreadLocal<>();
	
	public static void main(String[] args) throws Exception{
		LOCAL.set("doge");
		System.out.println(LOCAL.get());
	}
}

The above only describes the single-threaded situation and because the main thread ignores the step of Thread = new Thread (), if there are multiple threads, it will be slightly more complex, but the principle is unchanged. ThreadLocal instances always get the current threaded instance through Thread. current Thread (), and then operate the Thr in the threaded instance. The member variable of eadLocalMap type, therefore, is a bridge and does not have the storage function itself.

6. ThreadLocal Source Code Analysis

For ThreadLocal source code, we need to focus on set(), get(), remove().

1. Internal attributes of ThreadLocal

//Get the hash magic number of the next ThreadLocal instance
private final int threadLocalHashCode = nextHashCode();

//Atomic counters, mainly to the extent that they are defined as static
private static AtomicInteger nextHashCode = new AtomicInteger();

//Hash Magic Number (Growth Number) is also the correction of the golden section of the signed 32-bit integer value.
private static final int HASH_INCREMENT = 0x61c88647;

//Generate the next hash magic number
private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

One thing to note here is that threadLocalHashCode is a final property, while the atomic counter variable nextHashCode and the method for generating the next hash number nextHashCode() are static variables and static methods, and static variables are initialized only once. In other words, for each new ThreadLocal instance, the internal threadLocal HashCode increases by 0x61c88647. For instance:

//The threadLocalHashCode variable in t1 is 0x61c88647
ThreadLocal t1 = new ThreadLocal();
//The threadLocalHashCode variable in t2 is 0x61c88647 + 0x61c88647
ThreadLocal t2 = new ThreadLocal();
//The threadLocalHashCode variable in t3 is 0x61c88647 + 0x61c88647 + 0x61c88647 + 0x61c88647 + 0x61c88647
ThreadLocal t3 = new ThreadLocal();

ThreadLocal HashCode is the core variable of the hashing algorithm used in the ThreadLocal Map structure below. For each ThreadLocal instance, its threadLocal HashCode is unique.

2. Basic Structure and Source Code Analysis of Internal Class ThreadLocalMap

ThreadLocal inner class ThreadLocalMap uses default modifiers, which are accessible by packages (private packages). A static class Entry is defined inside ThreadLocalMap. Let's focus on the source code of ThreadLocalMap, starting with members and structure:

/**
 * ThreadLocalMap Is a custom hash map that is only applicable to maintaining thread local variables.
 * All of its methods are defined within the ThreadLocal class.
 * It is package private, so ThreadLocalMap can be defined as a variable in the Thread class.
 * To handle very large (value) and long-term uses, the Key of the hash table uses WeakReferences.
 * When the queue of references (weak references) is no longer used, the corresponding expired entries can be removed from the hash table by active deletion.
 */
static class ThreadLocalMap {
 
        //Note that the Key of Entry here is WeakReference < ThreadLocal <?>.
	static class Entry extends WeakReference<ThreadLocal<?>> {
        
		//This is the real stored value.
		Object value;
                // The Key of Entry is the ThreadLocal instance itself, and the Value is the input value.
		Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
	}
        //Initialization capacity must be a power of 2
	private static final int INITIAL_CAPACITY = 16;

        //Entry tables must be expanded when necessary, and the length must be a power of 2.
	private Entry[] table;

        //Number of elements (Entry) in a hash table
	private int size = 0;
 
        //The next threshold to be expanded is 0 by default.
	private int threshold;
   
        //Set the next threshold that needs to be expanded and set the value to two-thirds of the input value len
	private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }
    
    // Increase i by using len as a module
    private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }
     
    // Reducing i by using len as a module
    private static int prevIndex(int i, int len) {
        return ((i - 1 >= 0) ? i - 1 : len - 1);
    }
}

It is important to note here that ThreadLocalMap$Entry is WeakReference and the key value Key is ThreadLocal <?> instance itself, where an infinite generic wildcard is used.

Next, look at the constructor of ThreadLocalMap:

// The example method void createMap (Thread, T first Value) corresponding to ThreadLocal is used when constructing ThreadLocal.
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    // Hash table default capacity is 16
    table = new Entry[INITIAL_CAPACITY];
    // Calculate the hash code of the first element
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

// When constructing Inheritable ThreadLocal, the contents of the ThreadLocal Map based on the parent thread are extracted and put into the hash table of the new ThreadLocal Map.
// Static ThreadLocal Map CreateInheritedMap (ThreadLocal Map parentMap) corresponding to ThreadLocal
private ThreadLocalMap(ThreadLocalMap parentMap) {
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    setThreshold(len);
    table = new Entry[len];
    // Copy Hash Table Based on Parent ThreadLocalMap
    for (Entry e : parentTable) {
        if (e != null) {
            @SuppressWarnings("unchecked")
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if (key != null) {
                Object value = key.childValue(e.value);
                Entry c = new Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null)
                    h = nextIndex(h, len);
                table[h] = c;
                size++;
            }
        }
    }
}

Note here that when the set() method of ThreadLocal is called, it is lazy to initialize a ThreadLocalMap and put the first element in it. The private construction of ThreadLocalMap is provided for the static method ThreadLocal#createInheritedMap().

Then look at some of the example methods that ThreadLocalMap provides ThreadLocal to use:

// This method is called if Key cannot find a hash slot in the hash table
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
    // Here we try to traverse the entire hash table through nextIndex, and return to Entry if we find a matching Key.
    // If there is a case of Key = null in the hash table, call expungeStaleEntry to clean it up
    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;
}

// 1. Empty the Key and Value of the staleSlot corresponding hash slot
// 2. Rehash all possible conflicting hash table slots between staleSlot and the next empty hash slot, and empty the slot whose Key is null.
// 3. Note that the return value is the hash code of the next empty hash slot after staleSlot
private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    
    // expunge entry at staleSlot
    // Empty the Key and Value of the staleSlot corresponding hash slot
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;

    // Rehash until we encounter null
    // The following procedure is to re-hash all possible conflicts between staleSlot and the next empty hash slot, and empty the slot whose Key is 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;
}

// This method is long enough to replace the value of Entry in the hash slot where the hash code is staleSlot.
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;
    // The main purpose of this loop is to find the hash code of the first Key null hash slot before 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
    // After traversing staleSlot, the hash slot is replaced by an input value if the Key matches
    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) {
            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 doesn't match, create a new hash slot
    // If key not found, put new entry in stale slot
    tab[staleSlot].value = null;
    tab[staleSlot] = new Entry(key, value);
    
    // Here, if the current staleSlot is inconsistent with finding the preceding slotToExpunge, a clean-up is performed.
    // If there are any other stale entries in run, expunge them
    if (slotToExpunge != staleSlot)
        cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}

// Call expungeStaleEntry for Entry with null for all keys in the current hash table
private void expungeStaleEntries() {
    Entry[] tab = table;
    int len = tab.length;
    for (int j = 0; j < len; j++) {
        Entry e = tab[j];
        if (e != null && e.get() == null)
            expungeStaleEntry(j);
    }
}

// Clean up the N hash slots after the first hash slot. If the Key of Entry is null, n will reset to the length of the hash table. ExpungeStale Entry may reset the hash to change the length of the hash table.
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;
}


/**
 * This method mainly calls `ThreadLocal#get()', and gets the corresponding Entry in the hash table through the current ThreadLocal instance.
 *
 */
private Entry getEntry(ThreadLocal<?> key) {
    // Calculate the hash value of Entry
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i]; 
    if (e != null && e.get() == key)
        return e;
    else  // Note here that if e is null or Key is wrong, getEntry AfterMiss is called
        return getEntryAfterMiss(key, i, e);
}

// Re-hash and expand if necessary
private void rehash() {
    // Clean up all empty hash slots and re-hash them
    expungeStaleEntries();

    // Use lower threshold for doubling to avoid hysteresis
    // Trigger expansion when the number of hash elements of hash table is greater than 3/4 threshold
    if (size >= threshold - threshold / 4)
        resize();
}

// Expansion, Simple Expansion of 2-fold Capacity        
private void resize() {
    Entry[] oldTab = table;
    int oldLen = oldTab.length;
    int newLen = oldLen * 2;
    Entry[] newTab = new Entry[newLen];
    int count = 0;

    for (Entry e : oldTab) {
        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;
}

// Based on ThreadLocal as key, the current hash table is set. This method is called by `ThreadLocal set ()'.
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);
    // Variable hash table
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        // Key matching, setting values directly
        if (k == key) {
            e.value = value;
            return;
        }
        // If Entry's Key is null, replace the Key as the current key and set the value
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    // Clean up the hash slot subscribed to the sz section of the current new settings element, and trigger expansion if the cleaning is successful and the sz is larger than the threshold value
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

Simply put, ThreadLocalMap is the real data storage container of ThreadLocal. In fact, all the logic of the complex part of ThreadLocal data operation is carried out in ThreadLocalMap. The ThreadLocalMap instance is a member variable of Thread, which sets the thread reality to the current execution when the ThreadLocal set () method is first invoked. In the example. If multiple ThreadLocal instances are used in the same thread, in fact, each ThreadLocal instance corresponds to a hash slot in the hash table of the ThreadLocalMap. For example, multiple ThreadLocal instances are used in the main function thread:

public class ThreadLocalMain {

	private static final ThreadLocal<Integer> TL_1 = new ThreadLocal<>();
	private static final ThreadLocal<String> TL_2 = new ThreadLocal<>();
	private static final ThreadLocal<Long> TL_3 = new ThreadLocal<>();

	public static void main(String[] args) throws Exception {
		TL_1.set(1);
		TL_2.set("1");
		TL_3.set(1L);
		Field field = Thread.class.getDeclaredField("threadLocals");
		field.setAccessible(true);
		Object o = field.get(Thread.currentThread());
		System.out.println(o);
	}
}

In fact, the hash table in the threadLocals attribute of the main thread is not only the three ThreadLocals defined above, because it is possible to use ThreadLocal elsewhere when loading the main thread. The result of Debug is as follows:

Simplify with PPT drawings:

In fact, threadLocalHashCode is an attribute in ThreadLocal@XXXX. It is obvious that threadLocalHashCode is a member variable of ThreadLocal.

The source code of ThreadLocal Map is simply and roughly analyzed in pipelining. Some detailed graphs are given below to illustrate the process of some core operations in ThreadLocal and ThreadLocal Map.

3. Creation of ThreadLocal

From the constructor of ThreadLocal, the construction of the ThreadLocal instance does not do anything, just to get a generic instance of ThreadLocal, which can be used as the key of ThreadLocalMap$Entry later:

// Note that threadLocal HashCode has been determined at the same time as the construction of each new `ThreadLocal'instance
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

// Using Supplier to Cover the Initial Value Method
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
    return new SuppliedThreadLocal<>(supplier);
}

// Default public constructor
public ThreadLocal() {

}

Note that threadLocal HashCode has been determined at the same time as the construction of each new ThreadLocal instance, which is also the hash slot bound value of the Entry hash table.

4. TreadLocal set Method

The source code for the set() method in ThreadLocal is as follows:

public void set(T value) {
	//Always get the current thread instance before setting the value
    Thread t = Thread.currentThread();
	//Getting threadLocals properties from the current thread instance
    ThreadLocalMap map = getMap(t);
    if (map != null)
	     //If the threadLocals attribute is not null, the key is overwritten as the current ThreadLocal instance with a value
         map.set(this, value);
    else
	//If the threadLocals attribute is null, a ThreadLocalMap is created, and the Key of the first item is the current ThreadLocal instance with a value.
        createMap(t, value);
}

// Here you see that the ThreadLocalMap instance is always fetched from the member variables of the thread instance
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

// When you create a ThreadLocalMap instance, you assign the new instance to the threadLocals member of the thread instance
void createMap(Thread t, T firstValue) {
     t.threadLocals = new ThreadLocalMap(this, firstValue);
}

The process source code above is very simple. When setting a value, you always get the current thread instance first and manipulate its variable threadLocals. The steps are:

  • Gets an instance of the current running thread.
  • Thread Local Maps (Thread Local Maps) are obtained from the thread instance, and if null, a new Thread Local Map instance is created and assigned to threadLocals.
  • Set the value through threadLocals and override if the original hash slot already has a value.

5. TreadLocal get method

The source code of get() method in ThreadLocal is as follows:

 public T get() {
   //Get an instance of the current thread
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
    //Get the Entry in ThreadLocalMap from the current ThreadLocal instance using the getEntry method of ThreadLocalMap
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T) e.value;
             return result;
            }
        }
    //If the threadLocals in the thread instance are null, the initialValue method is called and the ThreadLocalMap is created to assign values to threadLocals.
    return setInitialValue();
}

private T setInitialValue() {
    // Call the initialValue method to get the value
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    // ThreadLocalMap is created once if it is not initialized, and the value is set directly if it is initialized.
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

protected T initialValue() {
     return null;
}

The initial Value () method returns null by default, and if the ThreadLocal instance has not used the set() method to use the get() method directly, the entry with the ThreadLocal as Key in the ThreadLocal Map sets the value to the return value of the initial Value () method. If you want to change this logic, you can override the initialValue() method.

6. TreadLocal remote method

The source code for the remove() method in ThreadLocal is as follows:

public void remove() {
	//Get the ThreadLocalMap in the Thread instance
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
	   //Remove ThreadLocal Map elements based on the current ThreadLocal as Key
       m.remove(this);
}

Initialization of ThreadLocal.ThreadLocalMap

Let's look at the variables in the java.lang.Thread class:

public class Thread implements Runnable {

  //Pass the ThreadLocalMap variable in ThreadLocal
  ThreadLocal.ThreadLocalMap threadLocals = null;
  //Pass the ThreadLocalMap variable in Inheritable ThreadLocal
  ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}

That is, the data that ThreadLocal needs to store and retrieve is actually bound to the member variable threadLocals of the Thread instance, and is lazily loaded when the ThreadLocal set () method is called. It can be understood in conjunction with the content of the previous section, but it is not expanded here.

8. Under what circumstances will the use of ThreadLocal lead to memory leaks

In fact, ThreadLocal itself does not store any data, but the data in ThreadLocal is actually stored in the thread instance. In fact, it is thread memory leak. At the bottom, the member variable threadLocals in Thread object holds a large number of K-V structures, and threadLocals are always active, which results in the variable threadLocals. Unable to release and be recycled. The premise that threadLocals hold a large number of K-V structures is that there are many definitions of ThreadLocal instances. Generally speaking, it is impossible for an application to define a large number of ThreadLocals, so the general source of leakage is that threadLocals are always active and the variable threadLocals cannot be released and recycled. But we know that the Key of the Entry structure in ThreadLocalMap uses weak references (. WeakReference < ThreadLocal <?>).) When no strong references are used to refer to ThreadLocal instances, the GC of the JVM will reclaim these keys in the ThreadLocalMap. At this time, some keys in the ThreadLocalMap will appear null, but Value is not null. The Entry items, which will always reside in ThreadLocalMap if they are not cleaned up on their own initiative. That is why there are blocks of code that clean up the ThreadLocalMap instance key as null in get(), set(), and remove() methods in ThreadLocal. In summary, memory leaks can occur in the following areas:

  • ThreadLocal instances are initialized in large numbers (statically), and get(), set(), and remove() methods are no longer called after initialization.

  • A large number of ThreadLocals are initialized, which store large volumes of Value s, and threads using these ThreadLocal instances are always active.

One design highlight in ThreadLocal is the use of weak references by Key of the Entry structure in ThreadLocal Map. Imagine using strong references, which means that all data in ThreadLocalMap is bound to Thread's life cycle. This can easily lead to memory leaks due to the continuous activity of a large number of threads. If weak references are used, after the JVM triggers GC to reclaim weak references, ThreadLocal can delete those values whose Key is null in ThreadLocalMap by calling get(), set() and remove() methods the next time, thus playing the role of lazy deletion to release memory.

In fact, the Entry hash table constructed by ThreadLocal in setting up the internal class ThreadLocal.ThreadLocalMap has considered the memory leak problem, so the ThreadLocal.ThreadLocalMap$Entry class is designed as a weak reference and the class signature is static class Entry extends WeakReference < ThreadLocal <?>. As mentioned in the previous article, if the object associated with a weak reference is null, the weak reference will reclaim the object associated with a weak reference at the next GC. For instance:

public class ThreadLocalMain {

	private static ThreadLocal<Integer> TL_1 = new ThreadLocal<>();

	public static void main(String[] args) throws Exception {
		TL_1.set(1);
		TL_1 = null;
		System.gc();
		Thread.sleep(300);
	}
}

In this case, TL_1, the ThreadLocal, becomes null after the active GC, but the value in Entry is strongly referenced and the ThreadLoc_1 is stored in the Entry hash table in the ThreadLocal.ThreadLocalMap instance bound by threads, where the original TL_1 is in the hash slot Entry (inherited from WeakReference). The value of Al before recycling. These "isolated" hash slots Entry is the hash slot to be lazily deleted as mentioned earlier.

9. ThreadLocal Best Practices

In fact, ThreadLocal's best practices are simple:

  • Each time the ThreadLocal instance is used, its remove() method is called to clear the data in Entry.

The best time to call remove() method is to call it in the final block of code before the thread runs out, which can completely avoid memory leaks caused by improper operation. This active cleaning method is more effective than lazy deletion.

Ten. Summary

ThreadLocal Thread Local Thread Local Thread Local Thread Local Thread Local Thread Local Thread Local Thread Local Thread Local Thread Local Thread Local Thread Local Thread Local Thread Local Thread Local Thread Local Thread Local Thread Local Thread Local Thread The basic logic in ThreadLocal is not complicated, but once it involves performance impact, memory recovery (weak reference) and lazy deletion, it considers things that are relatively comprehensive and effective.

Java_supermanNO1: Focus on Java development technology research and knowledge sharing!

--END--

  • Comments (Editor is not easy, thank you for your support)
  • ...
  • Forwarding (Sharing Knowledge, Spreading Happiness)
  • ...
  • Focus on (updating Java development technology every day)
  • ...

Posted by the-hardy-kid on Mon, 16 Sep 2019 01:33:15 -0700