Make writing a habit together! This is the sixth day of my participation in the “Gold Digging Day New Plan · April More text Challenge”. Click here for more details.
If you are familiar with Java multi-threaded programming, you will be familiar with the design of ThreadLocal. If you are familiar with Java multi-threaded programming, you will be familiar with the design of ThreadLocal. If you are familiar with Java multi-threaded programming, you will be familiar with the design of ThreadLocal.
1 What is a ThreadLocal?
ThreadLocal is a thread’s local variable. We can use ThreadLocal to design variables that are accessible only from within the thread and are isolated from other threads.
2 What can ThreadLocal do?
The biggest feature of a ThreadLocal is that it is isolated from other threads. The data stored by the current thread ThreadLocal, and then the information stored in the ThreadLocal, can be directly obtained by the same thread without passing parameters, no matter how deep the call link of the program is.
3 Typical Application of ThreadLocal
A typical application scenario is the application of MyBatis paging plug-in, which needs the paging interface. It needs to put the paging information into ThreadLocal first, and directly obtain the paging information through ThreadLocal when needed, so that there is no need to pass the paging information to each interface and then pass it to the paging plug-in.
Spring also uses ThreadLocal to store transaction information in declarative transactions. Since setting up a transaction takes effect only if we use the same database connection, Spring’s transaction manager binds the database connection to a ThreadLocal and unbinds the transaction when it completes.
4 ThreadLocal source code parsing
The source code of ThreadLocal is relatively easy to read, so let’s start by looking at the principle of ThreadLocal’s set method
- First the set method gets the current thread T
- Gets the ThreadLocalMap object of the current thread from the current thread T
- Check whether ThreadLocalMap is empty
- If not null, set values for map, key for the current ThreadLocal object, and value for the parameter passed in
- When null, the ThreadLocalMap constructor is called to initialize the object
public class ThreadLocal<T> {...// ThreadLocal sets the value
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null)
map.set(this, value);
else
createMap(t, value);
}
// Get the ThreadLocalMap object of the current thread
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
When the current thread ThreadLocalMap object is empty, the ThreadLocalMap constructor is called to initialize the object
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap is the static inner class of ThreadLocal, and is the property of Thread
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
ThreadLocalMap is an attribute of a Thread, which means that data is stored on the Thread, which is the best way to understand its name.
Let’s look at the data structure of the static internal class ThreadLocalMap. We can see that the Map is much simpler than the HashMap. It is an array of entries that inherit from the WeakReference object (weak application, garbage collection cleaning). In addition, the Map does not use the zipper method to resolve hash conflicts. Instead, it takes the current subscript to find the next location that meets the conditions.
The class inheritance relationship of Entry is shown in the following figure. Key is a weak application, stored in the Reference object and obtained by get().
public class ThreadLocal<T> {...static class ThreadLocalMap {...static class Entry extends WeakReference<ThreadLocal<? >>{ Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; } } 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);
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if(e ! =null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if(key ! =null) {
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while(table[h] ! =null) h = nextIndex(h, len); table[h] = c; size++; }}}}private void set(ThreadLocal
key, Object value) {
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;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if(! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }private void replaceStaleEntry(ThreadLocal<? > key, Object value,int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot;
for (inti = prevIndex(staleSlot, len); (e = tab[i]) ! =null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
for (inti = nextIndex(staleSlot, len); (e = tab[i]) ! =null; i = nextIndex(i, len)) { ThreadLocal<? > k = e.get();if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// If key not found, put new entry in stale slot
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
if(slotToExpunge ! = staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }/** * Remove the entry for key. */
private void remove(ThreadLocal
key) {
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)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
}
}
Copy the code
InheritableThreadLocals is created by writing the inheritableThreadLocals property to the Thread’s getMap, createMap, and childValue methods. InheritableThreadLocals inherits the inheritableThreadLocals property of the parent Thread to transfer variables between parent threads.
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); }}Copy the code
5 Subtle design of ThreadLocal
-
Thread isolation
A ThreadLocal variable, as its name suggests, does not place data in a ThreadLocal object. Instead, it places a ThreadLocalMap on a Thread object. This allows for thread isolation.
-
Weak references resolve memory freeing of keys
ThreadLocal uses weak references to allow the ThreadLocal to be automatically collected by garbage collection when the external strong reference disappears, without freeing the reference to the key in the ThreadLocalMap.
Important: Keys can be released by weak references, but what about values?
If ThreadLocalMap cannot be effectively reclaimed, it is easy to have dirty data and even memory overflow. When ThreadLocal is used, we must call its remove method.
public static void main(String[] args) { new Thread(()-> { try { threadLocal.set("hello"); threadLocal.get(); } finally { threadLocal.remove(); } }).start(); } Copy the code
-
Parent threads pass ThreadLocal
While ThreadLocal is thread-isolated, it’s theoretically possible for a child thread to acquire values set by the parent thread, so we can use InheritableThreadLocal to do so.
InheritableThreadLocal is a subclass of ThreadLocal that overrides createMap, getMap, childValue, etc. The InheritableThreadLocal class has two member variables, one is threadLocals. One is inheritableThreadLocals, which checks if the parent thread has inheritableThreadLocals when the thread is created. Some of the locals will be inherited to implement the transfer from the parent thread to the child thread ThreadLocalMap.
6 ThreadLocal Test cases
The following is the simplest way to use ThreadLocal, and you can verify it by tracing the source code yourself.
package com.zhj.interview;
public class Test18 {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
private static ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
inheritableThreadLocal.set("hello main");
new Thread(()-> {
try {
threadLocal.set("hello");
System.out.println( Thread.currentThread().getName() + threadLocal.get());
System.out.println( Thread.currentThread().getName() + inheritableThreadLocal.get());
inheritableThreadLocal.set("hello thread1");
System.out.println( Thread.currentThread().getName() + inheritableThreadLocal.get());
} finally{ threadLocal.remove(); }},"thread1 - ").start();
new Thread(()-> {
try {
Thread.sleep(10);
System.out.println( Thread.currentThread().getName() + inheritableThreadLocal.get());
} catch (InterruptedException e) {
e.printStackTrace();
} finally{ threadLocal.remove(); }},"thread2 - ").start(); }}Copy the code
Thank you for reading, if you feel helpful, please click a thumbs-up, thanks a million!!