What is the
ThreadLocal is not a Thread, as the name suggests. It is the local variable maintenance class of Therad. The effect is to privatize variables (providing a copy of the variable for each Thread), thus achieving variable isolation between threads. For example, if you have a variable called count, manipulating count++ when multiple threads are running concurrently will cause thread-safety problems. With ThreadLocal count, however, you can create a count copy for each thread that belongs only to the current thread and operate on its own copy without affecting other threads. We first have a concept, specific or look at the source code (JDK1.8).
The principle of source
Simple usage
public static void main(String[] args) {
ThreadLocal<String> a = new ThreadLocal<String>();
a.set("1");
a.set("2");
System.out.println(a.get());
}
// The output is 2. It looks like the 1 is overwritten.
Copy the code
Let’s look at the set(T value) method.
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*
*/
public void set(T value) {
// The current thread
Thread t = Thread.currentThread();
/ / get ThreadLocalMap
ThreadLocalMap map = getMap(t);
// create map if it is null, set if it is not null
if(map ! =null)
map.set(this, value);
else
createMap(t, value);
}
// Assign t. htreadlocals to an instance of ThreadLocalMap.
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue); } 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);
}
/** * Set the resize threshold to maintain at worst a 2/3 load factor. */
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/* The current thread is in the ThreadL class */ for the ThreadLocalMap instance
ThreadLocal.ThreadLocalMap threadLocals = null;
Copy the code
ThreadLocalMap is an internal class to TreadLocal. It does not implement the Map interface, but it has several properties: Entry[] table, size, Threshold, INITIAL_CAPACITY, and java.util.hashMap are very similar. A more detailed explanation of these attributes can be found in this in-depth tutorial on how HashMap works. It is also mentioned in the class comment that it is a hash map customized to hold local thread values. Its key is the current instance of ThreadLocal, this, and its value is the parameter value of set. Since it is a Hash map, hash conflicts can occur. Let’s review common methods for resolving hash conflicts
-
Rehash: If the index already has a value, hash again. If not, hash again until an empty index position is found.
-
** Open address method: ** If the hash index already has a value, the algorithm looks for empty Spaces in several positions before or after it.
-
Create a public overflow zone: Put conflicting hash values in another overflow zone.
-
Chained address method: Stores hash values that generate hash conflicts in the index position in a linked list. HashMap solution.
ThreadLocalMap uses the open address method. If the current position has a value, it continues to find the next position. Note that the next position of table[len-1] is table[0], which is like an array of rings. If you can’t find the empty space all the time, an infinite loop will occur, and memory will overflow. Of course, there is a mechanism to expand capacity, won’t find empty position commonly.
/** * 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 {
static class Entry extends WeakReference<ThreadLocal<? >>{
/** The value associated with this ThreadLocal. */Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}/** * The table, resized as necessary. * table.length MUST always be a power of two
private Entry[] table;
/** * The number of entries in The table. ** The actual number of entries in The table */
private int size = 0;
/** * The next size value at which to resize. * table The next size value at which to resize
private int threshold;
/** * The initial capacity -- MUST be a power of two. */
private static final int INITIAL_CAPACITY = 16;
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal
key, Object value) {
Entry[] tab = table;
int len = tab.length;
// Calculate key index. ThreadLocalHashCode & (len-1) is equivalent to key.threadLocalHashCode%len
// But ampersand is more efficient than %. They are equivalent because len is 2 to the NTH power.
ThreadLocalHashCode does not affect the ability to read this code; more on that later
int i = key.threadLocalHashCode & (len-1);
// Open the address method, loop TAB
for(Entry e = tab[i]; e ! =null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<? > k = e.get();// If the key is the same, update value
if (k == key) {
e.value = value;
return;
}
// If the key is empty, the ThreadLocal instance is reclaimed and replaced with a new key-value
if (k == null) {
replaceStaleEntry(key, value, i);
return; }}//table[I]=null Create an Entity, ++size
tab[i] = new Entry(key, value);
int sz = ++size;
if(! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }/ / sorting table
private void rehash(a) {
// Delete old elements from table[]
expungeStaleEntries();
//size is still larger than 3/4 threshold
if (size >= threshold - threshold / 4)
resize();
}
/** * Expunge all stale entries in the table. * Delete table[] all entity */ with key==null
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); }}/** * Double the capacity of the table. */
private void resize(a) {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
// Create a new array with twice the capacity
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if(e ! =null) {
// If the threadThreadLocal<? > k = e.get();if (k == null) {
e.value = null; // Help the GC
} else {
// Evaluates the new array index
int h = k.threadLocalHashCode & (newLen - 1);
while(newTab[h] ! =null)
h = nextIndex(h, newLen);
newTab[h] = e;
count++;
}
}
}
setThreshold(newLen);
size = count;
table = newTab;
}
// Returns the ThreadLocalMap of the current thread
ThreadLocalMap getMap(Thread t) {
returnt.threadLocals; }}Copy the code
Look at the set method, get method
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get(a) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if(e ! =null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
returnresult; }}return setInitialValue();
}
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue(a) {
T value = initialValue();//null
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null)
map.set(this, value);
else
createMap(t, value);
return value;
}
// Default value null
protected T initialValue(a) {
return null;
}
Copy the code
Source summary:
In general, the ThreadLocal source code is relatively easy to understand. ThreadLocalMap is defined in ThreadLocal, but is referenced by thread.threadlocals. This ensures that a Thread has a separate ThreadLocalMap, isolated from other threads. The key of a ThreadLocalMap is a ThreadLocal instance and the value is a thread variable.
Take a look at the original source code.
public static void main(String[] args) {
ThreadLocal<String> a =new ThreadLocal<String>();
a.set("1");
a.set("2");
System.out.println(a.get());
}
// The output is 2. It looks like the 1 is overwritten.
// The key for Thread. ThreadLocals is a, and the last value to get is also the last value
The internal implementation of a single thread looks something like this
ThreadLocal<String> a =new ThreadLocal<String>();
Map map = new HashMap();
map.put(a,"1");
map.put(a,"2");
System.out.println(map.get(a));
Copy the code
Summary of problems in the source code
- Hash conflict in ThreadLocalMap
ThreadLocalMap resolves hash collisions by opening addresses. ThreadLocalHashCode is not explained in detail, but it is explained here.
// Compute the array subscript
int i = key.threadLocalHashCode & (len-1);
private final int threadLocalHashCode = nextHashCode();
** * The next hash code to be given out. Updated atomically. Starts at * zero
private static AtomicInteger nextHashCode = new AtomicInteger();
/** * getAndAdd(v) returns nextHashCode, but nextHashCode+=HASH_INCREMENT; * /
private static int nextHashCode(a) {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
/** * The difference between successively generated hash codes - turns * implicit sequential thread-local IDs into Near-optimally spread * multiplicative hash values for power-of-two-sized tables. * Self-increment */
private static final int HASH_INCREMENT = 0x61c88647;
Copy the code
Since nextHashCode is static, each new ThreadLocal() increments the HASH_INCREMENT value of the Fibonacci hash so that the hash code is evenly distributed in a 2 ^ N array. This is one reason why tables have a size of 2 to the n.
-
Memory leaks & weak references
Improper use of ThreadLocal can result in memory leaks, which in turn can lead to memory leaks **,
- Memory leak: Garbage objects are not collected in time or cannot be collected. Usually, garbage objects have incorrect references, resulting in memory waste. The increasing number of garbage objects may lead to memory overflow.
- Memory overflow: Not enough memory is available for the applicant to use.
Of course, any misoperation can lead to memory leaks or other bugs, but we’re only talking about ThreadLocal here.
Review the relationship between Thread, ThreadLocal, and ThreadLocalMap.
- Thread.threadlocals references ThreadLocalMap and has the same lifecycle.
- Definition of ThreadLocal ThreadLocalMap
- ThreadLocalMap#Entry weakly references ThreadLocal. When we say that an object is not referenced, it will be collected by GC, we are talking about strong references. But weak reference objects are garbage collected whether they are referenced or not.
When a Thread is destroyed, the instance of ThreadLocalMap that thread.threadlocals points to will also become garbage, and the Entity stored in it will also be reclaimed. There is no memory leak.
Memory leaks usually occur in Thread pools, threads have long lifetimes, and threadLocals references are always present. When a ThreadLocal is reclaimed, its Entity becomes an instance of key== NULL, which is not reclaimed. If the Entity is never get(), set(), or remove(), it is never reclaimed, and a memory leak occurs. ThreadLocal is normally called remove() when it is finished using it.
If the Entity key is null, the Entity will be released and garbage collected. If the Entity key is null, the Entity will be garbage collected.
Application scenarios
Its application scenarios are mainly as follows
- Thread-safe, wrapped thread-unsafe utility classes such as
java.text.SimpleDateFormat
Class, of course, jdK1.8 already provides the corresponding thread-safe classjava.time.format.DateTimeFormatter
- Thread isolation, such as database connection management, Session management, MDC log tracking, and so on.
ThreadLocal has recently been used to interface with the front end. The front-end adds toekN to the header when requesting the back-end interface, and the interceptor obtains user information via token, which is stored in ThreadLocal. The main code is as follows:
// Filter is used first for an interface request
public boolean checkUserLogin(String token){
UserDTO user = getUserByToken(token);
ContextUtil.setUserId(user.getId());
}
public class ContextUtil {
private static ThreadLocal<String> userIdHolder = new ThreadLocal();
/ / store userid
public static void setUserId(String userId) {
userIdHolder.set(userId);
}
public static String getUserId(a) {
return(String)userIdHolder.get(); }}// Actually call the interface
void invokeInterface(a){ String userId = ContextUtil.getUserId(); . }Copy the code
Each interface request is a thread that verifies that the interface is valid and stores the userID in ThreadLocal for later use.
conclusion
The principle and application of ThreadLocal are explained in depth through source code. Of course my ability is general, the level is limited, unavoidably some fallacy. Please be more considerate and welcome correction. Where there is feedback, there is growth.