define
Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).
Each thread holds an implicit reference to a copy of its thread-local variable as long as the thread is active and an instance of ThreadLocal is accessible. After a thread disappears, all copies of its thread-local variables are garbage collected (unless there are other references to those copies)
Does each thread hold an implicit reference to a copy of its thread-local variable?
From a Thread perspective, each Thread object has a threadLocals property.
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
Copy the code
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 instances are typically private static fields in a class that you want to associate state with a thread (for example, a user ID or transaction ID).
For example, the following code produces a unique ID for each thread, which is assigned on the first call to threadid.get () and remains the same in subsequent calls.
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0);
// Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
@Override protected Integer initialValue(a) {
returnnextId.getAndIncrement(); }};// Returns the current thread's unique ID, assigning it if necessary
public static int get(a) {
returnthreadId.get(); }}Copy the code
conclusion
- Thread:
ThreadLocal
As the name implies, it is not a thread, but a localized object of the thread. Used when working with objects in multiple threadsThreadLocal
When maintaining variables,ThreadLocal
Assign a separate copy of the variable to each thread that uses it. - Local:
ThreadLocal
Class allows us to create variables that can only be read and written by the same thread. As a result,If a piece of code contains oneThreadLocal
Even if two threads execute this code at the same time, they cannot access each other’sThreadLocal
The variable. So each thread can change its own copy independently without affecting the corresponding copy of other threads. From a thread’s perspective, this variable is like a thread’s Local variable, which is what “Local” in the class name means. - Thread data isolation: ThreadLocal provides a copy of a thread’s local variable. Each thread can manipulate this local variable through set() and get() without interfering with other threads’ local variables. Each thread that accesses a Threadlocal variable has a local copy of it. The variables that populate the Threadlocal belong to the current thread and are isolated from other threads.
- Stateful data is the sameStep:
ThreadLocal
To provide local variables within a thread that operate for the lifetime of the thread, reducing the complexity of passing some common variables between functions or components within the same thread.Not to solve the multithreading problem, but to solve the problem of variable sharing within a single thread
The data structure
A ThreadLocal can store only one Object. If you need to store multiple Object objects, you need to store multiple ThreadLocal, as shown in the following figure:
ThreadLocalMap
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.
How do I resolve hash conflicts?
Int I = key.threadLocalHashCode& (len-1)
The hash algorithm in ThreadLocalMap is simple, where I is the array subscript position of the current key in the hash table. The key here is the calculation of threadLocalHashCode, which has an attribute of HASH_INCREMENT = 0x61C88647. This is a special value, it’s the Fibonacci number or the golden ratio number. If the hash increment is this number, the advantage is that the hash distribution is very uniform.
Since the data structure of ThreadLocalMap is different from that of HashMap, which uses chained addresses, the conflict resolution approach is different from that of ThreadLocalMap, which uses open addresses.
Open address method:
When we insert data into a hash table, if some data has been used after the hash function, we start at the current location and look for free space until we find it.
Different conflict resolution schemes under open address method:
- Linear detection method
- Square detection method
- Double hash
Linear detection method
For example:
32%7 = 4;
13%7 = 6; 49%7 = 0; The next storage address (6 + 1) % 7 = 0 is still in conflict, and the next storage address (6 + 2) % 7 = 1 is not in conflict, can be stored.
Linear detection requires a large hash space, but it also has a problem: if more elements calculate the same hash, you can expect a primary clustering, where there is room but they all clusterin one place. There will be more and more conflicts in the gathering places, and the detection time will be longer and longer. The diagram below:
Square detection method
In order to solve the clustering problem, the idea of the square detection method is that the detection does not jump back one by one.
Skip detection, so that a cluster is avoided
But it also has a small problem, is the key hash to the same location after the detection path is the same. For many keywords that fall in the same place, the later the element is inserted, the longer the probe takes. This phenomenon is known as secondary clustering
Double hash
Secondary clustering occurs because we adopt a dependent function (square function) to detect keywords in the same position, which will not change the detection path due to the difference of keywords or other factors.
- Then you can make the detection method depend on the keyword, and then build another Hash function (hash2) to Hash the keyword in the same position again, and then use the Hash value to detect the detection, that is, double Hash.
- Since the Hash2 function is different from Hash1, the probability that two different keyword Hash1 values and Hash2 values are the same at the same time becomes very low. This avoids double clustering, but at the expense of calculating another hash function, Hash2.
Rehashing
- When there are too many hash elements (that is, the loading factor α is too large), the search efficiency will decrease. The maximum loading factor is generally 0.5 <= α<= 0.85
- When the loading factor is too large, the solution is to double the hash table, a process called Rehashing.
Note: When the hash table expands, the original elements need to be recalculated and placed into the new table
ThreadLocalMap handles hash collisions usingLinear detection methodTherefore, you cannot simply set entry to NULL when deleting a key. The method it uses is to rehash each subsequent entry that is not null and place it in the appropriate position to ensure that the deletion will not lead to the interruption of linear probe invalidation.
Specific reference source code:
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(); . }private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
Copy the code
How to expand capacity?
ThreadLocalMap cleans up before expanding,
There are two ways to clear:
- ExpungeStaleEntry () exploratory cleanup
- CleanSomeSlots () heuristic cleaning
Exploratory cleanup: Cleanup starts with the currently encountered GC elements and continues backward. Until null is encountered
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 expired * ThreadLocal uses weak references, i.e. multiple states. (recovered, not recovered) so it is not safe to implement according to R algorithm */
while(tab[h] ! =null) h = nextIndex(h, len); tab[h] = e; }}}return i;
}
Copy the code
After exploratory cleaning, the array should be partially cleared of expired elements, and the Entry elements that Hash should be closer to the actual Hash positions. The efficiency of search is improved. In this case, probing cleaning does not remove all expired elements in the array. Instead, it cleans up from the incoming subscript until the first Entry== NULL. Partial clearance. The rest needs to be cleared by heuristics
Heuristic cleanup:
Heuristically scan some cells looking for stale entries. This is invoked when either a new element is added, or another stale one has been expunged. It performs a logarithmic number of scans, as a balance between no scanning (fast but retains garbage) and a number of scans proportional to number of elements, that would find all garbage but would cause some insertions to take O(n) time.
Tentatively scan some cells for expired elements, that is, elements that have been 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
Note: ThreadLocal callsset()
.get()
.remove()
Key = null is cleared for value
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
Entry[] The expansion threshold of the array islen * 2 / 3
, two-thirds of the length of the array.
The initial capacity of ThreadLocalMap is 16
private static final int INITIAL_CAPACITY = 16;
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
Copy the code
Before expanding the array, a full cleanup is performed. Fori is used to iterate over every element in the array. If an expired Entry is found, exploratory cleanup is performed.
private void rehash(a) {
expungeStaleEntries();
if (size >= threshold - threshold / 4)
resize();
}
private void expungeStaleEntries(a) {
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
After the comprehensive cleaning, it will further determine whether the array length meets size >= threshold-threshold /4, that is, the real threshold before expansion is judged to be len * 2/3 * 3/4, that is, the real threshold value is 1/2 of the array length.
Each expansion increases the array size by twice, then iterates through the old array, recalculates the subscripts of the elements in the old array, and inserts the new array. If a Hash conflict occurs during insertion, traverse backwards looking for empty space.
private void resize(a) {
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;
}
Copy the code
How do I clear expired keys?
The set method of ThreadLocalMap retrieves the value (that is, the concrete instance) of an Entry object with a null key by calling the replaceStaleEntry method (which also calls heuristic and exploratory cleanup) Object itself to prevent memory leaks
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();if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return; }}...Copy the code
We should actively call the remove method to clean up when we are not using it.
try {
// Other business logic
} finally{threadLocal object. remove(); }Copy the code
A weak reference
The Entry of a ThreadLocalMap refers to a ThreadLocal as a weak reference (the dotted line in the data structure diagram). The weakly referenced objects are collected during GC, avoiding the problem that ThreadLocal objects cannot be collected
Here’s a review of Java object references
- Strong references: New objects are not recycled as long as the reference is present
- Soft reference: reclaim before memory overflow occurs
- Weak references: Survive until the next garbage collection occurs
- Virtual references: The purpose is to receive a system notification when an object is reclaimed by the collector
/** * ThreadLocalMap is a customized hash map suitable only for * maintaining thread local values. No operations are exported * outside of the ThreadLocal class. The class is package private to * allow declaration of fields in class Thread. To help deal with * very large and long-lived usages, the hash table entries use * WeakReferences for keys. However, since reference queues are not * used, stale entries are guaranteed to be removed only when * the table starts running out of space. */
static class ThreadLocalMap {
/** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. * /
static class Entry extends WeakReference<ThreadLocal<? >>{
/** The value associated with this ThreadLocal. */Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}Copy the code
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.
Why are ThreadLocal variables declared static?
The purpose of the ThreadLocal class is to maintain the value of a variable separately for each thread, preventing competing threads from accessing the same variable. It is suitable for situations where a variable needs to have its own value in each thread.
Declaring ThreadLocal non-static creates a new object for every instance of a ThreadLocal variable, which makes no sense and only increases memory consumption.
InheritableThreadLocal
ThreadLocal is fine, but the child thread cannot fetch the parent thread’s ThreadLocal variable:
private static ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
integerThreadLocal.set(1001); // father
new Thread(() -> System.out.println(Thread.currentThread().getName() + ":"
+ integerThreadLocal.get())).start();
}
//output:
Thread-0:null
Copy the code
When using a ThreadLocal, you can’t inherit from a parent thread’s ThreadLocal. When using an InheritableThreadLocal, you can pass data between parent threads. InheritableThreadLocal inherits ThreadLocal.
private static InheritableThreadLocal<Integer> inheritableThreadLocal =
new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
inheritableThreadLocal.set(1002); // father
new Thread(() -> System.out.println(Thread.currentThread().getName() + ":"
+ inheritableThreadLocal.get())).start();
}
//output:
Thread-0:1002
Copy the code
How does this work? Implementing local variable sharing between parent and child threads requires tracing the Thread object’s constructor:
private Thread(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null"); }...this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext = acc ! =null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
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 */
this.tid = nextThreadID(); . }Copy the code
Other ThreadLocal implementations
Netty FastThreadLocal:
ThreadLocal is an array structure that stores data in the JDK. ThreadLocalHashCode looks for Entry in the array and continues to detect lookups forward when hash conflicts occur, so retrieval is less efficient when hash conflicts occur. FastThreadLocal takes care of this problem, keeping the time complexity O(1). For reference: here
TransmittableThreadLocal:
TransmittableThreadLocal is Alibaba’s open source InheritableThreadLocal extension that solves the problem of passing ThreadLocal when using components such as thread pools that cache threads.
TransmittableThreadLocal(TTL) : provides the function of transmitting ThreadLocal values when the execution components of pooled and multiplexed threads such as thread pools are used to solve the problem of context transmission during asynchronous execution. A Java standard library this should provide the standard for the frame/middleware infrastructure development ability, the library function focus & 0, 17/16 support Java / 15/14/13/12/11/10/9/8/7/6.
The InheritableThreadLocal class in the JDK completes value passing from parent thread to child thread. But in the case of execution components that are pooled and reused by threads, such as thread pools, threads are created by thread pools and are pooled and reused; In this case, parent-child ThreadLocal value passing is meaningless, and the application actually needs to pass the ThreadLocal value when the task is submitted to the thread pool to the task execution time.
Memory leaks
A 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 will be null key entries in the ThreadLocalMap, and there will be no way to access the values of these null key entries. If the current thread does not terminate any longer, The value of these entries with a null key will always have a strong reference chain:
Thread Ref -> Thread -> ThreaLocalMap -> Entry ->value
It can never be reclaimed and may cause a memory leak.
ThreadLocalMap<null,value> if the thread is not terminated, the key of the ThreadLocalMap will be discarded if there is no strong reference. As a result, the value corresponding to the NULL cannot be reclaimed, which may lead to leakage.
The root cause of a ThreadLocal memory leak is that since ThreadLocalMap has the same lifetime as Thread, memory leaks occur if the corresponding key is not manually removed, not because of weak references.
While ThreadLocalMap largely solves potential memory leaks through replaceStaleEntry and heuristic and exploratory cleaning, it’s a good idea for developers to get used to it. We should actively call the remove method to clean up when we are not using it.
Spring related
ThreadLocal is used in many places within the Spring framework to aid implementation, such as transaction management. However, Spring does not guarantee multithreaded bean security at all.
- The root cause of thread-safety issues for each bean is the design of each bean itself.
- Do not declare any stateful instance or class variables in the bean. If you must, use ThreadLocal to make the variables thread-private.
- If instance variables or class variables of a bean need to be shared between multiple threads, methods such as synchronized, Lock, CAS, and so on are the only options for thread synchronization.
Best practices
- ThreadLocal does not solve the problem of multiple threads sharing variables
- ThreadLocal is perfect if you want to keep variables isolated between threads and shared between methods
- Saves thread context information, available wherever needed
- Thread-safe, to avoid the performance penalty of having to synchronize with thread-safe situations
- We should actively call the remove method to clean up when we are not using it.
reference
- Github.com/Snailclimb/…
- www.cnblogs.com/crazymakerc…
- Juejin. Cn/post / 684490…
- www.cnblogs.com/zhjh256/p/1…
- Yloopdaed. Icu / 2020/12/13 /…
- Github.com/alibaba/tra…
- www.cnblogs.com/stevenczp/p…