ThreadLocal many students are confused about what it is and what it can be used for. But it happens a lot in interviews, so this time I’m going to take a look at ThreadLocal.

Let’s look at our — ThreadLocal class in the form of interview questions.

Q&a content

##1. Q: What about ThreadLocal? Can you tell me the main uses of it?

A: From the JAVA official description of the ThreadLocal class (defined in the sample code) : The ThreadLocal class is used to provide local variables within a thread. These variables can be accessed in a multithreaded environment (through get and SET methods) to ensure that the variables of each thread are relatively independent of the variables of other threads. ThreadLocal instances are typically of the private static type and are used to associate threads with thread contexts.

ThreadLocal is used to provide local variables within a thread. Different threads do not interfere with each other. These variables operate for the lifetime of the thread, reducing the complexity of passing common variables between multiple functions or components within the same thread.

The above can be summarized as follows: ThreadLocal provides local variables within a thread that can be retrieved anywhere and anytime within the thread, isolating other threads.

Sample code:

/** * This class provides thread-local variables. These variables differ from their normal counterparts * in that accessing a variable (via its GET orsetEach thread has its own local variable * which is independent of the initialized copy of the variable. ThreadLocal instances are typically private static fields in a class * that want to associate state with a thread (for example, a user ID or transaction ID). * For example, the following classes generate local identifiers that are unique to each thread. * * thread ID is on the first call UniqueThreadIdGenerator. GetCurrentThreadId () distribution, * won't change it in the subsequent calls. * <pre> * import java.util.concurrent.atomic.AtomicInteger; * public class ThreadId {* // atomic integer, Thread ID * private static final AtomicInteger nextId = new AtomicInteger(0); * // Thread ID of each Thread * private static final ThreadLocal<Integer> threadId = * new ThreadLocal<Integer>() {* @override  protected IntegerinitialValue() {*returnnextId.getAndIncrement(); * *}}; * * // Returns the unique Thread ID of the current Thread, allocated if necessary * public static intget() {*returnthreadId.get(); *} *} * </pre> * Each thread keeps implicit references to copies of its thread-local variables as long as the thread is active and ThreadLocal instances are accessible * All copies of its thread-local instances are garbage collected after the thread disappears (unless other references to those copies exist).  * * @author Josh Bloch and Doug Lea * @since 1.2 */ public class ThreadLocal<T> {····· ··Copy the code

/** * Custom hash code (only useful in ThreadLocalMaps) * can be used to reduce hash collisions */ private final int threadLocalHashCode = nextHashCode();

/** * generates the next hash codehashThe generation operation is atomic. Private static AtomicInteger nextHashCode = new AtomicInteger(); private static AtomicInteger nextHashCode = new AtomicInteger(); Private static final int HASH_INCREMENT = 0x61C88647; private static final int HASH_INCREMENT = 0x61C88647; /** * returns the next hash codehashCode
     */
    private static int nextHashCode() {
        returnnextHashCode.getAndAdd(HASH_INCREMENT); }...}Copy the code

The nextHashCode() method is an atomic class that keeps adding 0x61C88647, which is a very special number called Fibonacci Hashing, which is also called the golden section, That is, using this number as the increment of the hash value will make the hash table more evenly distributed.

##2. Q: What is the implementation of ThreadLocal and how does it prevent threads with different local variables from interfering with each other?

A: Normally, if I don’t look at the source code, I guess ThreadLocal is designed like this: Each ThreadLocal class creates a Map and uses the threadID threadID as the Map key and the local variable to be stored as the Map value, thus achieving the effect of value isolation for each thread. This is the simplest way to design, and it’s how ThreadLocal was designed in the earliest days of the JDK.

Each Thread maintains a hash table called ThreadLocalMap. The key of the hash table is the ThreadLocal instance itself, and the value of the hash table is the Object.

This design is the opposite of what we said at the beginning, and it has the following advantages:

1) After this design, the number of entries stored in each Map will be smaller, because the number of entries stored was previously determined by the number of threads, but now is determined by the number of ThreadLocal.

2) When a Thread is destroyed, the corresponding ThreadLocalMap is also destroyed, reducing memory usage.

A: When the get() operation is called to get the value stored in ThreadLocal for the current thread, the following is done:

1) Get the Thread object of the current Thread, and then get the ThreadLocalMap object maintained in this Thread object.

2) Check whether ThreadLocalMap exists:

If so, the current ThreadLocal is used as the key and the getEntry method in ThreadLocalMap is called to obtain the corresponding storage entity E. Find the corresponding storage entity E, obtain the value corresponding to the storage entity E, that is, we want the value of the current thread corresponding to this ThreadLocal, return the result value.

If not, prove that the thread has no maintained ThreadLocalMap object and call the setInitialValue method to initialize it. SetInitialValue Returns the initialized value.

The setInitialValue method does the following:

1) Call initialValue to get the initialized value.

2) Get the Thread object of the current Thread, and then get the ThreadLocalMap object maintained in this Thread object.

Check whether the current ThreadLocalMap exists:

If one exists, map.set is called to set this entity entry.

If not, createMap is called to initialize the ThreadLocalMap object and store this entity entry as the first value in ThreadLocalMap.

PS: The operations related to ThreadLocalMap will be explained in the next question.

Sample code:

The first call to this method occurs when a thread accesses the ThreadLocal value of this thread via {@link #get}, unless {@link #set} is called first, in which case, * {@code initialValue} will not be called by this thread. * Normally, this method is called at most once per thread, * but it can be called again, which happens after {@link #remove} is called, * followed by {@link #get}. * * <p> This method simply returns null {@code null}; * if a programmer wants a ThreadLocal ThreadLocal variable to have an initial value other than null, * This method must be overridden by subclass {@code ThreadLocal} * In general, * * @return the initialValue of ThreadLocal */ protected T initialValue() {return null; } / create a ThreadLocal * * * * @ # see withInitial (Java. Util. Function. : Supplier) * / public ThreadLocal () {} / * * * * If the current thread does not have this ThreadLocal variable, * * @return returns the value of the current Thread for this ThreadLocal */ public T get() {// Get the current Thread object Thread T = Thread.currentThread(); ThreadLocalMap map = getMap(t); // If (map! E threadLocalMap. Entry e = map.getentry (this); E if (e! T result = (T)e.value; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + return result; }} // If the map does not exist, the thread does not maintain a ThreadLocalMap object // call setInitialValue to initialize return setInitialValue(); } /** * a variant implementation of set, used to initialize the value initialValue, * Used instead of preventing the user from overwriting the set() method * * @return the initial value */ private T setInitialValue() {// Call initialValue to get the initial value T value = initialValue(); Thread t = thread.currentThread (); ThreadLocalMap map = getMap(t); // If (map! Set to set this entity entry map.set(this, value); Else // 1) The current Thread does not have a ThreadLocalMap object // 2) Call createMap to initialize the ThreadLocalMap object // 3) store this entity entry as the first value in ThreadLocalMap  createMap(t, value); // return value; } @param t the current Thread * @return the map of the current Thread */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; }Copy the code

When the set(T value) operation is called to set the value to be stored by the current thread in ThreadLocal, the following operation is performed:

1) Get the Thread object of the current Thread, and then get the ThreadLocalMap object maintained in this Thread object.

2) Check whether ThreadLocalMap exists:

If one exists, map.set is called to set this entity entry.

If not, createMap is called to initialize the ThreadLocalMap object and store this entity entry as the first value in ThreadLocalMap.

Sample code:

Most subclasses do not need to override this method, {@link #initialValue} instead of setting the value of the current thread's corresponding ThreadLocal * * @param value will be stored in the current thread's corresponding ThreadLocal * */ public void Thread T = thread.currentThread (); ThreadLocalMap map = getMap(t); // If (map! Set to set this entity entry map.set(this, value); Else // 1) The current Thread does not have a ThreadLocalMap object // 2) Call createMap to initialize the ThreadLocalMap object // 3) store this entity entry as the first value in ThreadLocalMap  createMap(t, value); } /** * create a ThreadLocalMap for the current Thread. ** @param t the current Thread * @param firstValue */ void createMap(Thread t, t firstValue) {t.htreadlocals = new ThreadLocalMap(this, firstValue);  }Copy the code

When the remove() operation is called to remove the value stored by the current thread in ThreadLocal, the following operation is performed:

1) Get the Thread object of the current Thread, and then get the ThreadLocalMap object maintained in this Thread object.

2) Check whether the current ThreadLocalMap exists. If so, call map.remove to delete the corresponding entity entry using the current ThreadLocal as the key.

Sample code: If {@linkplain #get read} is called * in the current thread, the ThreadLocal variable will be called by {@link #initialValue} to initialize again, * Unless this value value is set via the current thread built-in call {@linkplain #set set} * this may result in multiple calls to the {@code initialValue} method * * @since 1.5 */ public void in the current thread ThreadLocalMap m = getMap(thread.currentThread ()); ThreadLocalMap m = getMap(thread.currentThread ()); // If (m! Map. remove // Delete the corresponding entity entry m.remove(this) with the current ThreadLocal key; }

##4. A common operation on a ThreadLocal is a hash table of a ThreadLocalMap. Can you talk about the underlying implementation of ThreadLocalMap?

A: The underlying implementation of ThreadLocalMap is a custom custom HashMap hash table with the following core elements:

1 ) Entry[] table; : Indicates the underlying hash table, which needs to be expanded if necessary. The length of the underlying hash table table.

2 ) int size; : Actual number of key-value pair stored entries

3 ) int threshold; : Threshold for the next capacity expansion. Threshold = Len x 2/3 of the underlying hash table. If size >= threshold, traverse the table and delete the element whose key is null. If size >= threshold*3/4, expand the table. For details, see set(ThreadLocal<? > key, Object value).

The Entry [] table; The core element stored in the hash table is Entry, which contains:

1 ) ThreadLocal<? > k; : The ThreadLocal instance object currently stored

2 ) Object value; : Specifies the value of the current ThreadLocal

It should be noted that this Entry inherits WeakReference. Therefore, when ThreadLocalMap is used, if key == null is found, it means that this key ThreadLocal is no longer referenced. It needs to be removed from the ThreadLocalMap hash table. (See Q&a 5 for an explanation of weak references)

Sample code:

/** * ThreadLocalMap is a custom hashMap hash table that is only suitable for maintaining ThreadLocal values for * threads. Methods of this class are not exposed outside of the ThreadLocal class. * This class is private and allows the declaration of fields in the ThreadLocal class to help handle large memory and long life cycle uses. * Such custom hash table entity key-value pairs use weak reference WeakReferences as keys, * however, once the reference is no longer in use, * the corresponding key-value pair entities that are no longer in use are ensured to be removed and recycled only when space in the hash table is exhausted. */ static class ThreadLocalMap {/** * entity entries inherit WeakReference in this hash map, * using ThreadLocal as key. Note that when the key is null (i.e. entry.get() * == null) which means that the key is no longer referenced, the entity entry is removed from the hash table. */ static class Entry extends WeakReference<ThreadLocal<? >> {/** the value of the current ThreadLocal. Entry(ThreadLocal<? > k, Object v) { super(k); value = v; } /** * Private static final int INITIAL_CAPACITY = 16; * The length of the underlying hash table must be 2 to the power of n. */ private Entry[] table; /** * Entries. */ private int size = 0; /** * Private int threshold; // Default is 0. /** * Set the threshold for triggering capacity expansion. Threshold * Threshold = The length of the underlying hash table len * 2/3 */ private void setThreshold(int len) { threshold = len * 2 / 3; } private static int nextIndex(int I, int len) {return ((I + 1 < len)? i + 1 : 0); } private static int prevIndex(int I, int len) {return ((i-1 >= 0)? i - 1 : len - 1); }}Copy the code

The constructor of ThreadLocalMap is lazy-loaded, that is, it is initialized once (and only once) only if the thread needs to store the value of the corresponding ThreadLocal. The initialization steps are as follows:

1) Initialize the initial size of the underlying array table to 16.

ThreadLocalHashCode & (INITIAL_CAPACITY – 1) That is, the hash value of threadLocalHashCode % calculates the storage location of the entity in the form of the length of the hash table.

3) Store the current entity with key: current ThreadLocal value: the actual value to store

4) Set the actual number of stored elements size to 1

5) Set setThreshold(INITIAL_CAPACITY), which is 2/3 of the initial capacity 16.

Sample code:

/** * The ThreadLocalMaps constructor is lazily loaded, so we will only use it when there is at least one entry to the * entity. Initialization is created only once. */ ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) {// Initialize table initial capacity is 16 table = new Entry[INITIAL_CAPACITY]; // Calculate the storage location of the current entry // Calculate the storage location equivalent to: / / the hash value of the ThreadLocal threadLocalHashCode % the length of the hash table length int I = firstKey. ThreadLocalHashCode & (INITIAL_CAPACITY - 1); Table [I] = new Entry(firstKey, firstValue); // Set the actual number of stored elements to 1. // Set the threshold to 2/3 of the initial capacity 16. setThreshold(INITIAL_CAPACITY); }Copy the code

The get() operation of ThreadLocal actually calls the getEntry(ThreadLocal Key) method of ThreadLocalMap. This method is quickly used to get an entry of an entity with a key. Otherwise, The getEntryAfterMiss(ThreadLocal Key, int I, Entry E) method should be called to maximize direct hit performance. This method does the following:

1) Calculate the storage location of the entry to be obtained. The storage location calculation is equivalent to: the hash value of threadLocalHashCode % hash table length.

2) Obtain the corresponding entity Entry according to the storage location of the calculation. Check whether the corresponding entity Entry exists and whether the key is equal:

If there is an Entry of the corresponding entity and the corresponding key is equal, that is, the same ThreadLocal returns the Entry of the corresponding entity.

If there is no Entry or keys are not equal, call getEntryAfterMiss(ThreadLocal<? > key, int I, Entry e).

getEntryAfterMiss(ThreadLocal<? > key, int I, Entry e)

1) Obtain the underlying hash table array table and iterate over the position associated with the corresponding entity Entry to be searched.

2) Obtain the key ThreadLocal of the current traversal entry, compare whether the key is consistent, and return if so.

3) if the key is inconsistent and the key is null, it proves that the reference does not exist. This is because Entry inherits WeakReference, which is the pit brought by WeakReference. Call the expungeStaleEntry(int staleSlot) method to remove expired entity entries (this method is not explained separately; see the sample code for detailed comments).

4) If the key is inconsistent and the key is not empty, go through the next position and continue to search.

5) Return null if the traversal is still not found.

Sample code:

/** * get the corresponding entity entry based on the key. This method is quickly used to get an entity entry with a key. Otherwise, call getEntryAfterMiss. This is done to maximize direct hit performance * * @param key Current Thread local object * @return the entry of the entity corresponding to the key. If no entry exists, Return null */ private Entry getEntry(ThreadLocal<? > key) {// Calculate the storage location of the entry to be obtained // Calculate the storage location equivalent to: ThreadLocalHashCode % Length of the hash table int I = key.threadLocalHashCode & (table.length-1); Entry Entry e = table[I]; ThreadLocal if (e! = null && LLDB () == key) // Return Entry e; Return getEntryAfterMiss(key, I, e); return getEntryAfterMiss(key, I, e); } /** * This method is called when no corresponding entity entry can be found based on the key. * * @param key Current thread local object * @param I Storage location of this object in hash table index * @param e the entry entity object * @return the entry entity entry corresponding to the key. If none exists, null */ private entry getEntryAfterMiss(ThreadLocal<? > key, int i, Entry e) { Entry[] tab = table; int len = tab.length; // Loop through all entities in the current position while (e! = null) {// Get the current entry key ThreadLocal ThreadLocal<? > k = e.get(); If (k == key) return e; // Find the corresponding entry, but its key is null, then the reference does not exist // This is because entry inherits WeakReference, If (k == null) // Delete stale entry expungeStaleEntry(I); NextIndex (I, len); nextIndex(I, len); // Get the entity entry to the next position e = TAB [I]; } // return null; } /** * delete the expired entity at the corresponding location, * * @param staleSlot index of the known key = NULL position * @return index of the next key = null position of the expired entity location * */ private int expungeStaleEntry(int staleSlot) {// Get table Entry[] TAB = table; Int len = tab.length; TAB [staleSlot]. Value = null; tab[staleSlot] = null; size--; // Rehash is performed until Entry e = null; 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; While (TAB [h]! = null) h = nextIndex(h, len); tab[h] = e; } } } return i; } /** * Delete all expired entities */ 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); }}Copy the code

The set(T value) operation of a ThreadLocal map calls the set(ThreadLocal<? > key, Object value), which performs the following operations:

1) Obtain the corresponding underlying hash table table and calculate the storage location of threalocal.

2) Loop through the entities where the table pair should be located to find the corresponding threadLocal.

3) Obtain the threadLocal of the current location. If the key threadLocal is consistent, the corresponding threadLocal is proved to be found, and the new value is assigned to the value of the current entity Entry found, end.

4) If the current location of the key threadLocal is inconsistent and the key threadLocal is null, call replaceStaleEntry(threadLocal <? > key, Object value,int staleSlot), replace the entity where key == null is set to the current entity, end.

I TAB [I] = new Entry(key, value) I TAB [I] = new Entry(key, value) Since weak references cause this problem, call cleanSomeSlots(int I, int n) to clean up useless data (this method is not explained separately, please see the sample code for detailed comments). If there is no data to clear and the number of stored elements is still greater than the threshold, the rehash method is called to expand the capacity. (This method is not explained separately, please refer to the sample code with detailed comments.)

Sample code:

@param key Specifies the current thread local object. @param value Specifies the value to be set. > key, Object value) {// We don't use the quick setup method like get(), // because set() is rarely used to create a new entity, as opposed to replacing an existing entity, in which case the quick setup scheme often fails. Table Entry[] TAB = table; Int len = tab.length; Int I = key.threadLocalHashCode & (len-1); int I = key.threadLocalHashCode & (len-1); ThreadLocal for (Entry e = TAB [I]; e ! = null; E = TAB [I = nextIndex(I, len)]) {ThreadLocal<? > k = e.get(); If (k == key) {if (k == key) {e.value = value; / / end return; If (k == null) {replaceStaleEntry(key, value, I); replaceStaleEntry(key, value, I); / / end return; }} // The current position of k! = key && k ! I TAB [I] = new Entry(key, value); Int sz = ++size; // If there is no data to be removed and the number of elements is still larger than the threshold, expand the capacity. CleanSomeSlots (I, sZ) &&sz >= threshold) // Rehash (); } /** * when performing a set operation, obtain the corresponding key threadLocal and replace the expired entity * store the value in the corresponding key threadLocal entity. Whether or not the body * corresponds to the key threadLocal * * has a side effect, This method removes all expired entities in that location that correspond to nextIndex * * @param key Current Thread local object * @param value Current Thread local object stored value * @param */ private void replaceStaleEntry(ThreadLocal<? > key, Object value, int staleSlot) {// Get the corresponding underlying hash table Entry[] TAB = table; Int len = tab.length; Entry e; // Clean the entire table to avoid the danger of continuous hash growth due to garbage collection // that is, the hash table is not clean, when the GC comes, // Record the start position of the position to be cleared int slotToExpunge = staleSlot; For (int I = prevIndex(staleSlot, len); (e = tab[i]) ! = null; i = prevIndex(i, len)) if (e.get() == null) slotToExpunge = i; ThreadLocal (int I = nextIndex(staleSlot, len); (e = tab[i]) ! = null; i = nextIndex(i, len)) { ThreadLocal<? > k = e.get(); // If we find the key, then we need to exchange it with the new expired data to keep the hash order. If (k == key) {// Instead, place the value to be set in the expired entity e.value = value; tab[i] = tab[staleSlot]; tab[staleSlot] = e; SlotToExpunge == staleSlot if (slotToExpunge == staleSlot) slotToExpunge = I; // Start cleaning up stale data here cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; } // if we do not find an expired entity in a later lookup, SlotToExpunge = I; if (k == null && slotToExpunge == staleSlot) slotToExpunge = I; } // If the key is not found, the new entity to be set will be placed in the same place as the original expired entity. tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); If (slotToExpunge!) if (slotToExpunge! = staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); } /** * Heuristic scan to find some expired entities and remove, * this method is called when a new entity is added, * or when an expired element is removed. * If there is no expired data, the time complexity of this algorithm is O(log n) * if there is expired data, So the time complexity of this algorithm is O(n) * * @param I a certain entity is not expired location, from which the scan starts * * @param n scan control: Cells with {@code log2(n)} are scanned, * unless an expired entity is found, in which case extra cells with {@code log2(table.length)-1} are scanned. * When called insert, the value of this parameter is the number of stored entities, * But if the replaceStaleEntry method is called, this value is the length of the hash table * (note: All of these may affect n's weight to a greater or lesser degree * but this version is simple, fast, and seems to perform just fine) * * @return true returns true if any expired entities are removed. */ 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; } / hash table expansion method is * * * * first scan the entire hash table table, delete expired entity * to reduce the hash table size or expand the hash table size, */ private void rehash() {// Delete all expired entities expungeStaleEntries(); If the number of storage entities is greater than or equal to 3/4 of the threshold, expand the capacity. If (size >= threshold-threshold /4) resize(); } /** * The idea is similar to that of HashMap, Private void resize() {// Record old hash Entry[] oldTab = table; // Record old hash Entry[] oldTab = table; Int oldLen = oldtab.length; Int newLen = oldLen * 2; int newLen = oldLen * 2; // Create a new hash table Entry[] newTab = new Entry[newLen]; int count = 0; For (int j = 0; int j = 0; j < oldLen; ++j) {// Obtain the corresponding entity Entry e = oldTab[j]; // If the entity is not null if (e! = null) {// get the entity's corresponding ThreadLocal ThreadLocal<? > k = e.get(); If (k == null) {if (k == null) {if (k == null) {if (k == null) { // Help the GC} else {// If it is not an expired entity, recalculate the storage location according to the new length int h = k.htReadLocalHashCode & (newlen-1); // Store the entity in the last position of ThreadLocal while (newTab[h]! = null) h = nextIndex(h, newLen); newTab[h] = e; count++; }}} // When the location is reallocated, recalculate the Threshold setThreshold(newLen); // Record the actual number of stored elements size = count; Table = newTab; }Copy the code

The remove() operation of ThreadLocal actually calls the remove of ThreadLocalMap (ThreadLocal<? > key) method, which does the following:

1) Obtain the corresponding underlying hash table table and calculate the storage location of threalocal.

2) Loop through the entities where the table pair should be located to find the corresponding threadLocal.

3) Obtain the threadLocal of the current location. If the key threadLocal is consistent, the corresponding threadLocal is found. Delete the entity at this location.

Sample code:

Private void remove(ThreadLocal<? > key) {// Obtain the corresponding table Entry[] TAB = table; Int len = tab.length; Int I = key.threadLocalHashCode & (len-1); int I = key.threadLocalHashCode & (len-1); ThreadLocal for (Entry e = TAB [I]; e ! = null; E = TAB [I = nextIndex(I, len)]) {if (LLDB () == key) {e = TAB [I = nextIndex(I, len)]) { // Clear the entity expungeStaleEntry(I) at this location; / / end return; }}}Copy the code

5. Q: The storage entity Entry in ThreadLocalMap uses ThreadLocal as the key, but this Entry inherits WeakReference. Why is it designed like this?

A: First of all, before I answer this question, I need to explain what a strong quote is and what a weak quote is. We commonly use strong references in normal situations:

A a = new A();

B b = new B(); When a = null; b = null; After a period of time, the JAVA garbage collection mechanism GC will reclaim the allocated memory space of A and B.

But consider this:

C c = new C(b); b = null; When b is set to NULL, does that mean that after a certain period of time the GC can reclaim the memory allocated by B? The answer is no, because even if B is set to NULL, C still holds a reference to B, and it is a strong reference, so GC does not reclaim b’s allocated space, and it is neither reclaimed nor used, resulting in a memory leak.

## So how to handle it?

C = null; , WeakReference w = new WeakReference(b); . Because WeakReference is used, GC can recover the space originally allocated by B. Back at the ThreadLocal level, ThreadLocalMap uses a weak reference to a ThreadLocal as its key. If a ThreadLocal has no external strong reference to it, then the ThreadLocal will be reclaimed during GC. There is no way to access the values of these null-key entries. If the current thread does not terminate, the values of these null-key entries will always have a strong reference chain: Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value can never be reclaimed, causing a memory leak. Get (),set(), and remove() remove all null-key values from the ThreadLocalMap.

But these passive precautions are no guarantee against memory leaks:

Static ThreadLocal extends the lifetime of ThreadLocal and may cause memory leaks

Allocation using ThreadLocal without calling the get(),set(), and remove() methods results in a memory leak.

The apparent root cause of the memory leak is the use of weak references. Most of the articles on the web have focused on analyzing memory leaks caused by ThreadLocal’s use of weak references, but another question is also worth pondering: why weak references instead of strong references?

Let’s start with what the official document says:

To help deal with very large and long-lived usages, 
the hash table entries use WeakReferences for keys.
Copy the code

To cope with very large and long-running uses, hash tables use weakly referenced keys.

# We will discuss the following two cases:

Key uses strong references: The object referenced by ThreadLocal is reclaimed, but ThreadLocalMap also holds a strong reference to ThreadLocal. Without manual deletion, ThreadLocal will not be reclaimed, resulting in Entry memory leaks.

Key uses weak references: The object referenced by ThreadLocal is reclaimed, and since ThreadLocalMap holds a weak reference to ThreadLocal, ThreadLocal is reclaimed even without manual deletion. The value is cleared the next time ThreadLocalMap calls get(),set(),remove().

Since ThreadLocalMap has the same lifetime as Thread, if the corresponding key is not manually removed, memory leaks will occur, but using weak references provides an additional layer of protection: Weak reference ThreadLocal does not leak memory and the corresponding value is cleared the next time ThreadLocalMap calls get(),set(),remove().

Thus, the root cause of a ThreadLocal memory leak is that since ThreadLocalMap has the same lifetime as Thread, a memory leak would result if the corresponding key was not manually removed, not because of weak references.

Given the above analysis, we can understand the causes and consequences of ThreadLocal memory leaks, but how to avoid them?

Each time ThreadLocal is used, its remove() method is called to clean up the data.

In the case of thread pools, not cleaning up ThreadLocal in a timely manner is not only a memory leak problem, but more importantly, it can lead to problems with business logic. So using ThreadLocal is like unlocking a lock and cleaning it up when you’re done.

##6. Q: What is the difference between synchronized and ThreadLocal?

A: The ThreadLocal and synchronized keywords are both used to deal with concurrent access to variables by multiple threads, but they approach the problem in different ways.

ThreadLocal is a Java class that resolves conflicting access to variables from different threads by manipulating local variables in the current thread. So ThreadLocal provides a thread-safe mechanism for sharing objects that each thread owns a copy of.

Synchronized in Java is a reserved word that relies on the LOCKING mechanism of the JVM to achieve atomicity in access to functions or variables in critical regions. In synchronization, only one thread can access a variable at a time through the locking mechanism of the object. At this point, the variable used as a “locking mechanism” is shared by multiple threads.

The synchronized keyword adopts a “time for space” approach, providing a list of variables that different threads queue up to access. ThreadLocal, on the other hand, uses a “space for time” approach, providing each thread with a copy of the variable, allowing simultaneous access without affecting each other.

##7. Q: What are the current scenarios for ThreadLocal?

A: In general ThreadLocal addresses two types of problems:

Solve concurrency problems: Use ThreadLocal instead of synchronized to ensure thread-safety. Synchronization takes a time-for-space approach, while ThreadLocal takes a space-for-time approach. The former provides only one copy of a variable, which different threads queue to access, while the latter provides one copy of a variable for each thread, so it can be accessed simultaneously without affecting each other.

Solve data storage problems: ThreadLocal creates a copy of variables in each thread, so each thread can access its own internal copy of variables without interfering with each other. For example, if the data of a Parameter object needs to be used in multiple modules, it will obviously increase the coupling between modules if the method of Parameter passing is adopted. In this case we can use ThreadLocal.

Application scenarios:

##Spring uses ThreadLocal to address thread-safety issues

We know that in general, only stateless beans can be shared in a multithreaded environment, and in Spring, most beans can be declared singleton scoped. Is because Spring for some Bean (such as RequestContextHolder, TransactionSynchronizationManager, LocaleContextHolder, etc.) the china-africa thread-safe state using ThreadLocal for processing Make them thread-safe, because stateful beans can be shared across multiple threads.

Generally, Web applications are divided into three layers: the presentation layer, the service layer and the persistence layer. The corresponding logic is written in different layers, and the lower layer opens function calls to the upper layer through interfaces. In general, all program calls from receiving a request to returning a response belong to the same thread ThreadLocal. This is a good way to solve the thread-safety problem by providing a separate copy of variables for each thread. In many cases, ThreadLocal is simpler and more convenient than directly using synchronized synchronization to address thread-safety issues, and the resulting program has higher concurrency.

Sample code:

Public Abstract class RequestContextHolder {···· · private static final Boolean jsfPresent = classutils.isPresent ("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());

    private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
            new NamedThreadLocal<RequestAttributes>("Request attributes");

    private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
            new NamedInheritableThreadLocal<RequestAttributes>("Request context"); ...}Copy the code

ThreadLocal provides local variables within a thread that can be retrieved anywhere and anytime within the thread, isolating other threads.

The design of ThreadLocal is that each Thread maintains a ThreadLocalMap hash table. The key of the hash table is the ThreadLocal instance itself, and the value is the actual value to store Object.

A common operation with ThreadLocal is actually to operate on a ThreadLocalMap in a Thread.

The underlying implementation of ThreadLocalMap is a custom HashMap hash table. The threshold of ThreadLocalMap = len * 2/3 of the length of the underlying hash table. If the actual number of stored elements is greater than or equal to 3/4 of threshold, size >= threshold*3/4, the underlying hash table array table is expanded.

Hash table Entry in ThreadLocalMap [] Table stores the core element of Entry, stores the key of the instance object of ThreadLocal, and stores the value of the corresponding value of ThreadLocal. It should be noted that this Entry inherits WeakReference. Therefore, when ThreadLocalMap is used, if key == null is found, it means that this key ThreadLocal is no longer referenced. It needs to be removed from the ThreadLocalMap hash table.

ThreadLocalMap uses a weak reference to a ThreadLocal as its key. If a ThreadLocal has no external strong reference to it, then the ThreadLocal will be reclaimed during GC. Get (),set(), and remove() remove all null-key values from ThreadLocalMap. If we do not actively invoke the above operations, we will result in a memory leak.

To use ThreadLocal safely, you must call remove() after each use of ThreadLocal to clean up useless entries, just as you would unlock a lock after each use. This is especially important when operations use thread pools.

The difference between ThreadLocal and synchronized: The synchronized keyword uses a “time-for-space” approach to provide a list of variables that can be queued up by different threads. ThreadLocal, on the other hand, uses a “space for time” approach, providing each thread with a copy of the variable, allowing simultaneous access without affecting each other.

ThreadLocal mainly solves two types of problems: A. Solve concurrency problems: Use ThreadLocal instead of synchronization mechanism to solve concurrency problems. B. Solve the problem of data storage: For example, the data of a Parameter object needs to be used in multiple modules. If the method of Parameter passing is adopted, the coupling between modules will obviously be increased. In this case we can use ThreadLocal.