ThreadLocal
对象可以提供线程局部变量,每个线程Thread
拥有一份自己的副本变量,多个线程互不干扰。
ThreadLocal.ThreadLocalMap threadLocals = null;
//通过线程继承在异步场景下传递线程本地变量的值
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
注:以上代码为Thread类的成员变量。
1. ThreadLocal类数据结构
ThreadLocalMap的key
并不是ThreadLocal
本身,而是它的一个弱引用。
2.垃圾回收后key值状态
下图展示了 ThreadLocalMap的引用关系:
可以看到,key虽然是 ThreadLoacl的弱引用,但ThreadLocalMap强引用键值对,因此垃圾回收后有一下两种情况:
//垃圾回收后key值为null,导致内存泄漏
new ThreadLocal<>().set("def");
//垃圾回收时,ThreadLocal强引用仍然存在,key不为null
ThreadLocal<Object> threadLocal = new ThreadLocal<>();
threadLocal.set("def");
3.ThreadLocalMap如何解决
Hash 冲突?
ThreadLocalMap
中并没有链表结构,发生Hash冲突时会线性向后查找。
4.ThreadLocalMap.set()方法
1. 使用hash算法得到value的key值
2. 得到槽位为空直接放数据,不为空则继续向后探查
3.若在探查到过期数据之前就探查到entry为null或者key相等的槽位,则进行数据新增或者替换
4.若先探查到了过期数据槽位staleSlot
,则需要进行探测式数据清理工作,即向前迭代,得到过期数据的起始下标(遇到entry为null的槽位停止迭代)
5.结束过期元素探查后,继续向后遍历,如果找到key相等的entry数据,进行数据替换,并与当前过期槽位staleSlot交换位置,如果先遍历到entry为null的槽位,则
创建新的Entry
,替换stableSlot槽位
5.过期key的处理方法
1. 探测式清理
set()方法触发的过期key清理方法就是探测式清理操作
2. 启发式清理
在探测式清理过后如果除了staleSlot
以外,还发现了其他过期的slot
数据,就要进行启发式清理
启发式清理流程
注意:启发式清理操作中会调用探测式清理操作
5.ThreadLocalMap的扩容机制
在ThreadLocalMap.set()
方法的最后,如果执行完启发式清理工作后,未清理到任何数据,且当前散列数组中Entry
的数量已经达到扩容阈值(len*2/3)
,就开始执行rehash()
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
rehash()
具体实现:
private void rehash() {
expungeStaleEntries();
if (size >= threshold - threshold / 4)
resize();
}
注意:rehash()
的阈值是size >= threshold,resize()的阈值是size >= threshold*3/Local
由于ThreadLocalMap使用数组存放entry,因此每次扩容都需要创建新的数组
6.ThreadLocalMap.get()方法
1.
通过查找key
值计算出散列表中slot
位置,然后该slot
位置中的Entry.key
和查找的key
一致,则直接返回
2. slot
位置中的Entry.key
和要查找的key
不一致,继续往后查找,查找过程中如果遇到过期key,触发一次探测式数据回收操作,继续往后查找,直到遇到entry为null的槽位,说明没有此数据