Take a quick look at the ThreadLocal source code
Commonly used method
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("aa");
threadLocal.get();
threadLocal.remove();
Copy the code
ThreadLocal data structure
Thread class a type for ThreadLocal. ThreadLocalMap threadLocals instance variables, that is to say, each Thread has an own ThreadLocalMap.
A ThreadLocalMap has its own separate implementation and can simply treat its key as a ThreadLocal and its value as a value put into the code (the key is not actually a ThreadLocal itself, but a weak reference to it).
When a thread puts a value into a ThreadLocal, it stores the value into its own ThreadLocalMap, and reads the value using ThreadLocal as a reference to find the corresponding key in its own map, thus achieving thread isolation.
ThreadLocalMap has a similar structure to HashMap, except that HashMap is implemented by array + list, whereas ThreadLocalMap has no list structure and uses linear detection for Hash collisions. We also notice Entry, whose key is ThreadLocal
k, inheriting from WeakReference, which is also known as WeakReference type.
- Strong references: We often create objects that are strong references. As long as strong references exist, the garbage collector will never reclaim the referenced object, even when memory is low
- SoftReference: an object with a SoftReference modifier is called a SoftReference. The object to which a SoftReference points is reclaimed when its memory is about to overflow
- Weak references: objects decorated with WeakReference are called weak references. Whenever garbage collection occurs, if the object is only pointed to by weak references, it will be collected
- Virtual References: Virtual references are the weakest references and are defined in Java using PhantomReference. The only purpose of a virtual reference is to queue up notifications that an object is about to die
Memory leak problem
If our ThreadLocal object has no strong reference, the weakly referenced key is reclaimed, but the value is not. If the thread does not exit, the value will persist, which will cause a memory leak. Since a Thread has one ThreadLocalMap, the lifetime of a ThreadLocalMap is the same as that of a Thread.
Set method
The set() call determines whether the current Thread has a ThreadLocalMap of the member variables of the Thread class. 1. If map == null, the ThreadLocalMap of a Thread class will be initialized, and the index in the array will be calculated by using the key of ThreadLocal. Since it is the first initialization, there is no hash conflict, so it will be inserted directly. 2.map ! = null, get ThreadLocalMap directly, call the Map’s set() method, execute the related logic. The set() method of ThreadLcoalMap computes the Hash to get the array’s index.
- index
Node is empty
, directly new Entry(key,value), andinsert
. - index
The node is not empty
, a hash conflict occurs. backwardTraverse ` ` nextIndex ()
Search for a new Entry(key,value) until the index node is not nullInsert table [I]
. The value of I changes, and the following happens in order during the search- If the key values are the same, the ThreadLocal object directly updates the value
- If key==null, the reference may expire
replaceStaleEntry()
Method to replace expired data. Iterates forward with the current node, marking the expiration positionstaleSlot
.
- It is executed after insertion
CleanSomeSlots cleaning
Method, and returns whether expansion is required. The load factor is also 0.75. Double the capacity.
Set() if ThreadLocalMap does not exist
Let’s take a look at the flow when we first set using ThreadLocal in Thread.
ThreadLocal. In class classpublic void set(T value) {
// Get the current thread
Thread t = Thread.currentThread();
// Get the current map by thread
ThreadLocalMap map = getMap(t);
if(map ! =null)
map.set(this, value);
else
// Initialize one if it does not exist
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue); } ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) { table =new Entry[INITIAL_CAPACITY];
// Hash the array subscript
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// Key is a weak reference
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
Copy the code
How to resolve Hash conflicts in ThreadLocalMap?
Initialize the Map method by simply looking at the set() method. ThreadLocalMap is a hash stored Map. Since it’s a hash, there must be steps to resolve hash conflicts.
// Computes the hash to get the array subscript value
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
public class ThreadLocal<T> {
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode(a) {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
static class ThreadLocalMap { ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) { table =new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1; setThreshold(INITIAL_CAPACITY); }}}Copy the code
As you can see from the code, our key is threadLocalHashCode. Each time we create a new ThreadLocal object, its threadLocalHashCode increments HASH_INCREMENT = 0x61C88647. This value is very special. It is the Fibonacci number, also known as the Golden ratio number. If the hash increment is this number, the advantage is that the hash distribution is very uniform. If you’re interested, look up Fibonacci algorithms.
The solution** We simulate inserting a value of value=27, hash result index=4, but index=4 already exists, so we iterate backwards to find the node with null index=8, find no value, then insert the corresponding value. This is the classic hash conflict resolution algorithm,Linear detection method
.
Set() if ThreadLocalMap exists
Going back to our set method, what happens when we first initialize a ThreadLocalMap and then perform a set() operation? By observing the source code main logic in
ThreadLocalMap.java
private void set(ThreadLocal
key, Object value) {
Entry[] tab = table;
int len = tab.length;
// Evaluates the hash to get the array index
int i = key.threadLocalHashCode & (len-1);
// Exit the loop from the current index iteration array table[I]==null
for(Entry e = tab[i]; e ! =null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<? > k = e.get();// The key is equal, representing the same threadLocal object, directly updating the value
if (k == key) {
e.value = value;
return;
}
// Node key==null, indicating that an expired node is detected
if (k == null) {
// staleSlot is the location of the expired node
replaceStaleEntry(key, value, i);
return; }}// Insert or overwrite a node
tab[i] = new Entry(key, value);
int sz = ++size;
if(! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }Copy the code
For loop logic:
- Traverse the current
key
Value in the bucketEntry
The data is empty, which means there’s no data collision in the hash array. Pop outfor
Loop, directset
Data is stored in the corresponding bucket - if
key
Value in the bucketEntry
Data is not null- k = key
, indicating that the current
The set operation is a replacement operation that does the replacement logic and returns directly key = null
, indicating the current bucket locationEntry
If yes, go toreplaceStaleEntry()
Method (core method), and return
- k = key
for
After the loop is executed, continuing to execute indicates that the process of backward iteration is encounteredentry
fornull
In the caseEntry
fornull
Create a new one in the bucketEntry
object- line
++size
operation
- call
cleanSomeSlots()
Do a heuristic cleanup to clean up the hash arrayEntry
thekey
Expired data- If no data is cleared after the cleaning is completed, and
size
If the threshold (2/3 of the array length) is exceeded, proceedrehash()
operation - rehash()
A round of exploratory cleaning will be carried out to clear the expiration date
Key ‘, if after cleaning is completedsize >= threshold – threshold / 4, the actual expansion logic will be performed (expansion logic goes back)
- If no data is cleared after the cleaning is completed, and
The get method
Get method is relatively simple, the general logic is:
Gets the ThreadLocalMap object for Thread. Map == null, execute the set initializer method. Map ! = null, the getEntry method is executed. GetEntry: Computes the index value based on the hash. If the keys of table[I] are equal, the value is returned. If table[I]==null is not equal, hash conflicts are generated before and the search is completed until table[I]==null or value is returned after the search. If an expired node whose table[I]. Key is null is found during traversal, probe data is reclaimed.
We take theget(ThreadLocal1)
For example, passhash
After calculation, correctslot
The position should be 4, andindex=4
Data has been generated for the slot andkey
Value is not equal toThreadLocal1
, so we need to continue to iterate on the search. Iteration toindex=5
Data at this timeEntry.key=null
, triggers a probe data reclamation operation, and executesexpungeStaleEntry()
Method, after execution,The index 5, 8
Is recycled, andThe index of 6, 7
The data will move forward, and then continue to iterate toindex = 6
That’s when I found itkey
The values are equalEntry
Data, as shown in the figure below:
Reference: www.jianshu.com/p/134d72d37…