[Java Concurrent Programming] ThreadLocal, which is often tested in the interview, learning the super detailed source code
What is ThreadLocal? For what?
public class Thread implements Runnable {
//线程内部局部变量
ThreadLocal.ThreadLocalMap threadLocals = null;
//子线程继承父线程的变量
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}
The official JAVA definition of ThreadLocal class is as follows:
ThreadLocal functions:
ThreadLocal is used to provide local variables in threads. Different threads will not interfere with each other. This variable plays a role in the life cycle of threads and reduces the complexity of the transfer of some public variables between multiple functions or components in the same thread.
Simple use of ThreadLocal
/**
* 官方demo,测试ThreadLocal的用法
*
* @author Summerday
*/
public class ThreadLocalTest {
public static void main(String[] args) {
// main Thread
incrementSameThreadId();
new Thread(ThreadLocalTest::incrementSameThreadId).start();
new Thread(ThreadLocalTest::incrementSameThreadId).start();
}
private static void incrementSameThreadId() {
try {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread() + "_" + i + ",threadId:" + ThreadLocalId.get());
}
} finally {
// 使用后请清除
ThreadLocalId.remove();
}
}
}
class ThreadLocalId {
// 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() {
return nextId.getAndIncrement();
}
};
// Returns the current thread's unique ID,assigning it if necessary
public static int get() {
return threadId.get();
}
// remove currentid
public static void remove() {
threadId.remove();
}
}
//输出
Thread[main,5,main]_0,threadId:0
Thread[main,main]_1,main]_2,threadId:0
Thread[Thread-0,threadId:1
Thread[Thread-0,threadId:1
Thread[Thread-1,threadId:2
Thread[Thread-1,threadId:2
Implementation idea of ThreadLocal?
Source code analysis of ThreadLocal common methods
ThreadLocal. set(T value)
public void set(T value) {
//获取到当前的线程对象
Thread t = Thread.currentThread();
//进而获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//如果ThreadLocalMap存在,则以当前的ThreadLocal为key,value作为值设置entry
if (map != null)
map.set(this,value);
else
//调用createMap进行ThreadLocalMap对象的初始化,并将此实体作为第一个值
createMap(t,value);
}
//创建一个与线程t关联的ThreadLocalMap
void createMap(Thread t,T firstValue) {
t.threadLocals = new ThreadLocalMap(this,firstValue);
}
ThreadLocal. get()
public T get() {
//获取到当前的线程对象
Thread t = Thread.currentThread();
//进而获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//如果此map存在
if (map != null) {
//以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体e
ThreadLocalMap.Entry e = map.getEntry(this);
//找到对应的存储实体 e
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果map不存在,调用setInitialValue进行初始化
return setInitialValue();
}
private T setInitialValue() {
//调用initialValue获取初始化的值
T value = initialValue();
//获取当前线程对象
Thread t = Thread.currentThread();
//获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//如果此map存在
if (map != null)
//存在则调用map.set设置此实体entry
map.set(this,value);
else
////调用createMap进行ThreadLocalMap对象的初始化,并将此实体作为第一个值
createMap(t,value);
return value;
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocal. remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//以当前ThreadLocal为key删除对应的实体entry
m.remove(this);
}
It can be found that these methods of ThreadLocal find the corresponding map through the current thread. In fact, they all operate the threadlocalmap object maintained by them. These details will be discussed later.
Threadlocalmap source code analysis
Threadlocalmap structure analysis
The underlying implementation of threadlocalmap is a custom HashMap:
Entry inherits weak references, which may cause each data in entry [] table to have three states:
static class ThreadLocalMap {
/**
* 实体Entry在此hash map中是继承弱引用 WeakReference,* 使用ThreadLocal 作为 key 键.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** 当前 ThreadLocal 对应储存的值value. */
Object value;
Entry(ThreadLocal<?> k,Object v) {
super(k);
value = v;
}
}
/**
* 初始容量大小 16 -- 必须是2的n次方.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* 底层哈希表 table,必要时需要进行扩容.
* 底层哈希表 table.length 长度必须是2的n次方.
*/
private Entry[] table;
/**
* 实际存储键值对元素个数 entries.
*/
private int size = 0;
/**
* 下一次扩容时的阈值
*/
private int threshold; // 默认为 0
/**
* 设置触发扩容时的阈值 threshold
* 阈值 threshold = 底层哈希表table的长度 len * 2 / 3
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* 获取该位置i对应的下一个位置index
*/
private static int nextIndex(int i,int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* 获取该位置i对应的上一个位置index
*/
private static int prevIndex(int i,int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
}
Hash algorithm of threadlocalmap
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() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
//...省略get set remove等方法
static class ThreadLocalMap {
//2的幂 len % c = len & (c - 1)
private static final int INITIAL_CAPACITY = 16;
//ThreadLocalMaps是延迟加载的,在有entry实体存放时,才初始化创建一次。
ThreadLocalMap(ThreadLocal<?> firstKey,Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
// 哈希算法 , 利用数组容量2的幂次的特性,位运算快速得到位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey,firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
}
}
Threadlocalmap uses the linear detection method to solve hash conflicts. In fact, the entry [] table can be imagined as a ring logically. It can also be seen that nextindex and previndex are two methods to obtain the next position in the ring sense.
Whenever a ThreadLocal object is created, nexthashcode will grow by 0x61c88647. 0x61c88647 is a very special value, called Fibonacci number. It can make the hash distribution more uniform, and the next available slot can be detected faster during linear detection, so as to ensure efficiency.
ThreadLocalMap. set()
https://snailclimb.gitee.io/javaguide/#/docs/java/Multithread/ThreadLocal
private void set(ThreadLocal<?> key,Object value) {
//set方法需要考虑到hash碰撞,相对get方法比较复杂一些
Entry[] tab = table;
int len = tab.length;
//得到在table中的索引位置
int i = key.threadLocalHashCode & (len-1);
//循环遍历
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i,len)]) {
//获取当前位置的ThreadLocal
ThreadLocal<?> k = e.get();
//如果key一致,则直接赋予新值【替换操作】,并退出
if (k == key) {
e.value = value;
return;
}
//当前位置的key为null【过期数据】,调用replaceStaleEntry方法,并退出
if (k == null) {
replaceStaleEntry(key,value,i);
return;
}
}
//遍历过程中,遇到entry == null 的情况【没有数据冲突】,直接使用这个桶
tab[i] = new Entry(key,value);
int sz = ++size;
//调用cleanSomeSlots启发式清理操作,清理散列数组中Entry的key过期数据
//清理完成后,未清理到任何数据,且size超过了阈值,进行rehash
if (!cleanSomeSlots(i,sz) && sz >= threshold)
//rehash不等于resize!
rehash();
}
private void rehash() {
// 进行一轮探测式清理【全量清理】
expungeStaleEntries();
// 清理完成后,如果满足size >= threshold - threshold / 4,执行扩容逻辑
// threshold默认是 len * 2/3 size >= len / 2 默认执行扩容
if (size >= threshold - threshold / 4)
resize();
}
ThreadLocalMap. Resize() capacity expansion
//将表的容量加倍
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
//逐一遍历旧的哈希表的每一个entry,重新分配至新的哈希表中
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null; // 清除无效entry的value值,帮助GC回收
} else {
// 线性探测来存放Entry
int h = k.threadLocalHashCode & (newLen - 1);
while (newTab[h] != null)
h = nextIndex(h,newLen);
newTab[h] = e;
count++;
}
}
}
//重新计算阈值
setThreshold(newLen);
size = count;
table = newTab;
}
ThreadLocalMap. get()
private Entry getEntry(ThreadLocal<?> key) {
//通过key计算出散列表中的位置
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
//如果key一致,命中直接返回
if (e != null && e.get() == key)
return e;
else
//不一致,调用方法继续找
return getEntryAfterMiss(key,i,e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key,int i,Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
//遇到无效的slot,执行一次探测式的清理
if (k == null)
expungeStaleEntry(i);
else
//index后移
i = nextIndex(i,len);
e = tab[i];
}
return null;
}
ThreadLocalMap. remove()
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
// 计算对应threalocal的存储位置
int i = key.threadLocalHashCode & (len-1);
// 循环遍历table对应该位置的实体,查找对应的threadLocal
for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i,len)]) {
// 如果key threadLocal一致,则证明找到对应的threadLocal
if (e.get() == key) {
// 执行清除操作
e.clear();
// 清除此位置的实体
expungeStaleEntry(i);
// 结束
return;
}
}
}
ThreadLocalMap. replaceStaleEntry()
In the set method, when the bucket is searched circularly and the expired key is found, this method will be called. The replacestateentry method provides the function of replacing the expired data:
// 在执行set操作时,获取对应的key,并替换过期的entry
private void replaceStaleEntry(ThreadLocal<?> key,Object value,int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
// 往前找到table中第一个过期的实体的下标
// 清理整个table范围,避免因为垃圾回收带来的连续增长哈希的危险
// 记录slotToExpunge 开始探测式清理过期数据的开始下标
int slotToExpunge = staleSlot;
//向前遍历查找第一个过期的实体下标
for (int i = prevIndex(staleSlot,len);
(e = tab[i]) != null;
i = prevIndex(i,len))
if (e.get() == null)
slotToExpunge = i;
// 向后遍历查找 key一致的ThreadLocal或 key为null 的
for (int i = nextIndex(staleSlot,len);
(e = tab[i]) != null;
i = nextIndex(i,len)) {
ThreadLocal<?> k = e.get();
// 如果找到key,【将它跟新的过期数据交换】
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// 两个索引重合,表明在【整个扫描过程中】前+后扫描 的时候并没有找到过期的key
if (slotToExpunge == staleSlot)
//修改开始探测式清理过期数据的下标为当前循环的index
slotToExpunge = i;
// 从slotToExpunge开始做一次启发式清理
cleanSomeSlots(expungeStaleEntry(slotToExpunge),len);
return;
}
// 如果当前的slot已经无效,并且向前扫描过程中没有无效slot,则更新slotToExpunge为当前位置
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// 最后key仍然没有找到,则将要设置的新实体放置在原过期的实体对应的位置上
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key,value);
// 在探测过程中如果发现任何无效slot,则做一次清理(探测式清理+启发式清理)
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge),len);
}
Threadlocalmap expired key cleanup process
Probe cleanup expungestaleentry
The expungestaleentry method will traverse the hash array, probe backward from the starting position, clean up the expired data, set the entry of the expired data to null, and rehash the data and reposition it in the table if it encounters unexpired data along the way.
If the location already has data, the unexpired data will be placed in the bucket with entry = = null closest to this location, so that the entry data after rehash is closer to the correct bucket.
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// 因为entry对应的ThreadLocal已经被回收,value设为null,显式断开强引用
tab[staleSlot].value = null;
// 显式设置该entry为null,以便垃圾回收
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
//向后遍历
for (i = nextIndex(staleSlot,len);(e = tab[i]) != null;i = nextIndex(i,len)) {
ThreadLocal<?> k = e.get();
//遇到k == null 的过期数据,清空该槽位
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
// key没有过期,重新计算当前key的下标位置是不是当前槽位的下标位置
int h = k.threadLocalHashCode & (len - 1);
// 如果不是,说明产生了hash冲突
if (h != i) {
tab[i] = null;
//以新计算出正确的槽位位置往后迭代,找到最近一个可以存放entry的位置.
while (tab[h] != null)
h = nextIndex(h,len);
tab[h] = e;
}
}
}
//经过迭代之后,有hash冲突的数据的entry位置会更加靠近正确的位置,查询效率更高。
return i;
}
Heuristic cleanup cleansomeslots
Called when an entry is added or an expired element is cleared:
// i对应的entry是非无效的
// n用于控制扫描次数
private boolean cleanSomeSlots(int i,int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
//从i的下一个开始,因为i不是无效的
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;
}
ThreadLocal memory leak
We need to clarify the structure of threadlocalmap. The storage entity entry in threadlocalmap uses ThreadLocal as the key, but this entry inherits the weak reference WeakReference.
The difference between strong reference and weak reference
What is the difference between a weak reference and a strong reference? Review here: there are four types of references. Strong references belong to the first level and weak references belong to the third level.
for instance:
A a = new A();
a = null;
Since a = null, the Java garbage collection mechanism will reclaim the memory space corresponding to a after a period of time.
B b = new B(a);
a = null;
When a is set to null, GC will not reclaim a's memory space. The reason is that although a is null, object B still holds a strong reference to A. therefore, a memory leak occurs in a memory, because it cannot be reclaimed or used.
There are two solutions:
How is ThreadLocal memory leak caused?
Threadlocalmap uses the weak reference of ThreadLocal as the key. If a ThreadLocal has no external strong reference to reference it, the ThreadLocal is bound to be recycled during system GC. In this way, entries with null key will appear in threadlocalmap, and there is no way to access the values of these entries with null key.
If the current thread does not end for a long time, the values of these null key entries will always have a strong reference chain: thread ref - > thread - > threalocal map - > entry - > value can never be recycled, resulting in memory leakage.
This situation has been taken into account in the design of threadlocalmap, and precautions have been taken in the methods of get, set and remove: after the call, all values with null keys in threadlocalmap are cleared.
Prerequisites for memory leakage using ThreadLocal:
So why design to use weak references? Is strong quotation not fragrant?
This problem can be discussed from what problems will arise if strong references are used:
If the storage structure is defined in the form of strong reference, that is, the common key value, in essence, the node's life cycle is forcibly bound to the thread. As long as the thread is not destroyed, the node is always reachable in GC analysis and cannot be recycled.
The advantage of using weak references is that if a ThreadLocal is no longer reachable by strong references, it will be garbage collected, and the corresponding entry in threadlocalmap will become invalid, which facilitates the garbage cleaning of threadlocalmap itself.
What problems can ThreadLocal solve?
Solving concurrency problems
Use ThreadLocal instead of synchronized to ensure thread safety. The synchronization mechanism adopts the mode of "time for space", while ThreadLocal adopts the mode of "space for time". The former only provides a variable for different threads to queue up for access, while the latter provides a variable for each thread, so it can be accessed at the same time without affecting each other.
public class DateUtil {
private static ThreadLocal<SimpleDateFormat> format1 = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static String formatDate(Date date) {
return format1.get().format(date);
}
}
Solve data storage problems
ThreadLocal creates a copy of the variable in each thread, so each thread can access its own internal copy variable, and different threads will not interfere with each other. For example, the data of a parameter object needs to be used in multiple modules. If parameters are passed, it will obviously increase the coupling between modules. At this point, we can use ThreadLocal to solve the problem.
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
What is inheritablethreadlocal?
Inheritablethreadlocal is mainly used to automatically inherit the ThreadLocal variable of the parent thread when the child thread is created, so as to facilitate the further transmission of necessary information.
The implementation principle is that the child thread is created by calling the new thread () method in the parent thread, and the thread#init method is called in the thread construction method. Copy the parent thread data to the child thread in the init method
Thread initialization code:
/**
* 初始化一个线程.
* 此函数有两处调用,
* 1、上面的 init(),不传AccessControlContext,inheritThreadLocals=true
* 2、传递AccessControlContext,inheritThreadLocals=false
*/
private void init(ThreadGroup g,Runnable target,String name,long stackSize,AccessControlContext acc,boolean inheritThreadLocals) {
//......(其他代码)
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
//......(其他代码)
}
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
/**
* 构建一个包含所有parentMap中Inheritable ThreadLocals的ThreadLocalMap
* 该函数只被 createInheritedMap() 调用.
*/
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
// ThreadLocalMap 使用 Entry[] table 存储ThreadLocal
table = new Entry[len];
// 逐一复制 parentMap 的记录
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++;
}
}
}
}