What is ThreadLocal

ThreadLocal is used to hold thread global variables for easy invocation. That is, the current thread is unique and not shared with other threads. The variable can be retrieved from anywhere in the current thread.

Use of ThreadLocal

1. How to save content

Create a ThreadLocal instance, and call the set function to save the Chinese string, and obtain the value in the current thread and the new-thread thread respectively. By printing the result, you can see that the new-thread fetched the value null even though it referenced the same object.

Running results:

Main Chinese MainActivity: new-thread nullCopy the code

What’s going on here?

If a ThreadLocal is used for the first time by a ThreadLocal thread, you need to create an instance of ThreadLocalMap. Otherwise, it is saved directly through the set function of the ThreadLocalMap instance.

2. How to get content

Since the set function before the main thread saves the content to the ThreadLocalMap instance, the Chinese string is already available. In the new-thread thread, since ThreadLocalMap is used for the first time, the map is null and the setInitialValue function is called.

In the setInitialValue function, the initialValue function is called, which directly returns NULL, which is why the value obtained in the new-thread thread is NULL. So the setInitialValue function mainly creates a ThreadLocalMap object for the current thread.

3, ThreadLocalMap

ThreadLocalMap internally holds an array table that holds the Entry elements. Entry is inherited to WeakReference, and ThreadLcoal instance is taken as key, and saved content T is taken as value. When GC occurs, the key is reclaimed, causing the Entry to expire.

Each thread holds a ThreadLocalMap local variable, threadLocas, as shown in the figure below.

3.1 Creation of ThreadLocalMap

The creation of the ThreadLocalMap object, that is, the ThreadLocal object calls its own createMap function.

The constructor of ThreadLocalMap creates a table array that holds Entry objects. The default size is 16. The Entry’s subscript in the array is calculated by using the threadLocalHashCode attribute of threadLocal, which is saved, and 2/3 of INITIAL_CAPACITY is calculated.

The threadLocalHashCode attribute is automatically evaluated when the ThreaLocal object is created.

ThreadLocalHashCode, as the only instance variable of ThreadLocal, varies from instance to instance. Nexthashcode.getandadd defines the threadLocalHashCode value for the next instance of ThreadLcoal, The threadLocalHashCode value of the first ThreadLocal starts at 0 and is spaced HASH_INCREMENT with the next threadLocalHashCode.

Array subscripts computed by threadLocalHashCode & (len-1) are distributed evenly, reducing collisions. However, conflicts still occur. If a conflict occurs, the new Entry is placed where Entry =null on the back.

Third, source code analysis

The set function of ThreadLocalMap

In the previous section, we examined the set function of the ThreadLocal instance and ended up calling the set function of the ThreadLocalMap instance for saving.

The set function of ThreadLocalMap is divided into three main steps:

  1. Calculate the position of the current ThreadLocal in the table array, and then iterate backwards until the Entry is null. The Entry key traversed is equal to the current ThreadLocal instance, and value is directly replaced.

  2. If the iterated Entry is expired (the key of the Entry is null), the replaceStaleEntry function is called to replace it.

  3. After the traversal, if 1 or 2 does not appear, a new Entry is created and saved to the position where there is no Entry at the end of the array.

Expired entries are cleaned up in step 2 and at the end. We’ll look at this later. In step 2, the replaceStaleEntry function is called when an expired Entry is detected.

The replaceStaleEntry function is divided into two traversals, one forward traversal and one backward traversal, using the current expired Entry as the dividing line.

If an expired Entry is found during the traversal, slotToExpunge is retained until an Entry is null. This is just to judge whether there are expired entries in front of staleSlot, and then facilitate cleaning later.

If an Entry with the same key is found during the backward traversal, the value is directly exchanged with the Entry in the staleSlot position. If the same key is not encountered, a new Entry is created and saved to the staleSlot location. At the same time, if no expired Entry is found in the forward traversal and an expired Ntry is found in the backward traversal, slotToExpunge needs to be updated because slotToExpunge needs to be cleared later.

2. ThreadLocalMap clears expired entries

In the previous section, expungeStaleEntry and cleanSomeSlots functions were used to clean up expired entries. How are they implemented?

The process of expungeStaleEntry clearing expired entries is called probing clearing. The function passes in an argument to the position of an expired Entry. It first sets the position to NULL and then iterates through all the positions at the back of the number group. If any Entry is expired, null is directly set; otherwise, it is moved to the appropriate position: the hash computed position or the position closest to the hash position.

After this experience, no expired entry exists when the staleSlot position reaches the position where the last entry=null at the back, and each entry is either in the original hash position or the closest to the original hash position.

The expungeStaleEntry function works within:

The expungeStaleEntry function initially sets the starting point, the third position in the array, to NULL. Then it starts iterating through the back elements of the array, and positions 4 and 5 remain the same here whether they are in its hash position or not. When entry 6 is iterated, it is found that entry 6 has expired. Set entry 6 to NULL. Now positions 3 and 6 are white.

A. At the end of the 7th iteration, suppose h! If = I is true, the entry in position 7 will be moved to position 6, leaving position 7 empty.

B, then traverse to position 8, assuming h! = if I is not true, the position of entry 8 remains unchanged.

We continue iterating through the back elements, repeating steps A and B until we hit null entry and exit the loop. For example, position 10 here, entry= NULL.

Because of exploratory cleaning, it ends up hitting entry= NULL. The heuristic cleaning is carried out through the cleanSomeSlots function, which does not stop when entry= NULL, but is determined by the control condition N. In this process, when an expired entry is encountered, n reverts to the array length, increasing the cleaning scope.

In the process of heuristic cleaning, if an expired Entry is encountered, the control condition N will be restored to the array length len, resulting in an increase in the number of cycles. Then, the number of nextIndex will be increased, thus increasing the cleaning range. This method may not be able to completely clean up all the expired elements. For example, in the process of controlling the right shift of N, no expired entry is encountered, so it ends.

3. Capacity expansion mechanism of ThreadLocalMap

In Section 1, the set function of ThreadLocalMap is followed by the reHash function.

After the outer layer is heuristically cleaned, if size>threshold, a rehash will be performed. In the Rehash, expired entries in the entire array will be cleaned. If the array length is larger than 3/4*threshod after cleaning, the resize will be expanded.

The resize function directly creates a new array that is twice as long as the old one. Then recalculate the position of the old array elements in the new array and copy.

4. Memory leaks

Normally, you run out of ThreadLocal instances, set them to null, and the ThreadLocal object is reclaimed when GC occurs. However, if the thread is still alive (for example, thread pool thread reuse), the Entry value object will not be released, resulting in memory leakage. So, after using the ThreadLocal instance, call the remove function to clean it up.

confusion

When GC occurs, will the Key be reclaimed and will the value be retrieved?

Under normal circumstances, if the ThreadLocal instance is strongly referenced at the same time, it will not be reclaimed during GC, i.e. weakReference.get has a return value and will not be reclaimed.

Recommended reading: Java References and ThreadLocal