一、ThreadLocal 简介
ThreadLocal
顾名思义能够依据字面意思了解成线程本地变量。也就是说如果定义了一个 ThreadLocal
,每个线程都能够在这个ThreadLocal
中读写,这个读写是线程隔离的,线程之前不会有影响。
每个 Thread
都保护本人的一个ThreadLocalMap
,所以是线程隔离的。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
通过这个 ThreadLocalMap
实现数据的读写,既然是 Map
必定有 key
和value
,然而这个 ThreadLocalMap
的key
能够简略的看成是 ThreadLocal
,理论是并不是ThreadLocal
的自身,而是它的一个 弱援用。
二、ThreadLocal 学习纲要
学习纲要思维导图如下图:
三、ThreadLocal 办法和成员变量
API
ThreadLocal
的 API
很少就蕴含了 4 个,别离是get()
、set()
、remove()
、withInitial()
,源码如下:
public T get() {}
public void set(T value){}
public void remove(){}
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {}
get()
:获取以后线程对应的ThreadLocalMap
存储的值,key
为以后TheadLocal
(理论为TheadLocal
的弱援用),也就是获取以后线程本地变量的值。set(T value)
:给以后线程对应的ThreadLocalMap
的设置值,也就是给以后线程本地变量设置值。remove()
:革除火线程对应的ThreadLocalMap
存储的TheadLocal
,也就是革除以后线程本地变量的值。withInitial()
:用于创立一个线程局部变量,变量的初始化值通过调用 Supplier 的 get 办法来确定
成员变量
// 调用 nextHashCode()办法获取下一个 hashCode 值,用于计算 ThreadLocalMap.tables 数组下标
// key.threadLocalHashCode & (len - 1)
private final int threadLocalHashCode = nextHashCode();
// 原子类,用于计算 hashCode 值
private staitc AmoicInteger nextHashCode = new AmoicInteger();
// hash 增量值,斐波那契数也叫黄金分割数,能够让 hash 值散布十分平均
private static final int HASH_INCREMENT = 0x61c88647;// 获取下一个 hashCode 值办法,只用原子类操作
private static int nextHashCode () {return nextHashCode.getAndAdd(HASH_INCREMENT);
}
四、ThreadLocalMap
ThreadLocalMap
是 ThreadLocal
类的一个动态外部类,在下面有说到每个线程都保护着一个ThreadLocalMap
,这个`ThreadLocalMap
就是用来贮存数据的。
ThreadLocalMap
外部保护着一个 Entry
节点,这个节点继承了 WeakReference
类,泛型为 ThreadLocal
示意是弱援用,节点外部定义了一个为 Object
的value
,这个 value
就是咱们寄存的值,Entry
类的构造方法只有一个,传入 key
和value
,这个 key
就是 ThreadLocal
,理论为ThreadLocal
的弱援用。
static class Entry extends WeakReference<ThreacLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v){super(k);
value = v;
}
}
Thread、ThreadLocalMap、ThreadLocal 构造关系
每个 Thread
都有一个 ThreadLocalMap
变量,ThreadLocalMap
外部定义了 Entry
节点类,这个节点继承了 WeakReference
类泛型为 ThreacLocal
类,节点类的构造方法ThreadLocal<?> k, Object v
,所以能够失去上面的构造关系图:
GC 之后 key 是否为 null?
思考一个问题,既然 ThreadLocalMap
的key
是弱援用,GC
之后 key
是否为 null
?在搞清楚这个问题之前,咱们须要先搞清楚Java
的四种 援用类型:
- 强援用:
new
进去的对象就是强援用,只有强援用存在,垃圾回收器就永远不会回收被援用的对象,哪怕内存不足的时候。 - 软援用:应用
SoftReference
润饰的对象被称为软援用,在内存要溢出的时候软援用指向的对象会被回收。 - 弱援用:应用
WeakReference
润饰的对象被称为弱援用,只有产生垃圾回收,被弱援用指向的对象就会被回收。 - 虚援用:虚援用是最弱的援用,用
PhantomReference
进行定。惟一的作用就是用来队列承受对象行将死亡的告诉。
这个问题的答案是不为 null,能够看上面的图:
通过上图咱们晓得 ThreadLocal
的强援用是依然存在的,所以不会被回收,不为null
ThreadLocalMap 成员变量
// 初始化容量 必须为 2 的幂,位运算取代模运算晋升计算效率,能够试 hash 值产生碰撞的概率更小,尽可能的使
// 元素在哈希表中平均的散列
private static final int INITTAL_CAPACIRY = 16;
// Entry 表
private Entry[] table;
// Entry 表寄存的元素数量
private int size = 0;
// 扩容阙值
private int threshold;
五、ThreadLocal.set()办法源码详解
set()
办法用于给本地线程变量设值,咱们先来看看 set()
办法的源码,从源码来一步一步解析实现原理,源码如下:
pubic void set(T value) {
// 获取以后线程
Thread t = Threac.currentThread();
// 获取以后线程的 ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 如果 map 不为 null,调用 ThreadLocalMap.set()办法设置值
if (map != null)
map.set(this, value);
else
// map 为 null,调用 createMap()办法初始化创立 map
createMap(t, value);
}
// 返回线程的 ThreadLocalMap.threadLocals
ThreadLocalMap getMap(Thread t) {return t.threadLocals;}
// 调用 ThreadLocalMap 构造方法创立 ThreadLocalMap
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// ThreadLocalMap 构造方法,传入 firstKey, firstValue
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 初始化 Entry 表的容量 = 16
table = new Entry[INITIAL_CAPACITY];
// 获取 ThreadLocal 的 hashCode 值与运算失去数组下标
int i = firsetKey.threadLocalHashCode & (INITAL_CAPACITY - 1);
// 通过下标 Entry 表赋值
table[i] = new Entry(firstKey, firstValue);
// Entry 表存储元素数量初始化为 1
size = 1;
// 设置 Entry 表扩容阙值 默认为 len * 2 / 3
setThreshold(INITIAL_CAPACITY);
}
private void setThreshold(int len) {threshold = len * 2 / 3}
ThreadLocal.set()
办法还是很简略的,外围办法在 ThreadLocalMap.set()
办法,ThreadLocal.set()
办法流程如下:
- 获取以后线程的
ThreadLocalMap map
。 - 如果
map
不为null
则调用map.set()
办法设置值。 - 如果
map
为null
则调用createMap
办法创立。 createMap()
办法通过ThreadLocalMap
的构造方法创立,构造方法次要做了初始化Entry[] table
容量 16,通过ThreadLocal
的threadLocalHashCode
调用nextHashCode()
办法获取hashCode
值计算出下标,table
数组通过下标赋值,初始化存储的元素数量,初始化数组扩容阙值。
ThreadLocalMap
在构造方法里解决的时候用到了咱们学习纲要里说到的 hash
算法,源码如下:
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
private final int threadLocalHashCode = nextHashCode();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT);
}
这里最要害的就是 threadLocalHashCode
值的计算,ThreadLocal
中有一个属性为 HASH_INCREMENT = 0x61c88647
,没创立一个ThreadLocal
就会调用一次 nextHashCode()
办法,这个 HASH_INCREMENT
值就会增长 0x61c88647
,这个值很非凡,是斐波那契数也叫黄金分割数,这个值能够让hash
散布十分平均。
能够下一个小 demo 测试一下:
public static void main(String[] args) {
int hashCode = 0;
int HASH_INCREMENT = 0x61c88647;
for (int i = 0; i < 16; i++) {
hashCode = i * HASH_INCREMENT + HASH_INCREMENT;
int bucket = hashCode & (16 - 1);
System.out.println(i + "在桶中的地位:" + bucket);
}
}
下面测试输入如下:能够看出数据在算列数组中散布的很平均。
0 在桶中的地位:7
1 在桶中的地位:14
2 在桶中的地位:5
3 在桶中的地位:12
4 在桶中的地位:3
5 在桶中的地位:10
6 在桶中的地位:1
7 在桶中的地位:8
8 在桶中的地位:15
9 在桶中的地位:6
10 在桶中的地位:13
11 在桶中的地位:4
12 在桶中的地位:11
13 在桶中的地位:2
14 在桶中的地位:9
15 在桶中的地位:0
ThreadLocalMap.set()办法源码详解
ThreadLocalMap.set()
办法分为好几种状况,次要有以下四种状况,针对不同的状况咱们通过画图来阐明。
阐明:上面所有图中,绿色块
Entry
代表失常数据,灰色代表Entry
的key
值为null
,已被GC
回收,红色代表Entry
为null
。
第一种状况:通过 hash
计算失去的下标,该下标对应的 Entry
为null
:
这种状况间接将该数据放入该槽位即可。
第二种状况:通过 hash
计算失去的下标,该下标对应的 Entry
不为 null
,然而key
雷同:
这种状况间接该槽位的 value
值。
第三种状况:通过 hash
计算失去的下标,该下标对应的 Entry
不为 null
,且key
不雷同,这种时候会遍历数组,线性往后查找,查找 Entry
为null
的槽位,且在找到 Entry
为null
之前没有遇到 key
过期的 Entry
,就该数据放入该槽位中,如果遍历过程中,遇到了key
相等的槽位,间接更新 value
即可:
留神:每次循环查找都会判断 key
是否相等,如果相等则更新 value
间接返回。
第四种状况:基于第三种状况,如果在找到 Entry
为null
之前遇到了 key
过期的Entry
,如下图:
如上图散列数组下标为 7 地位对应的 Entry
数据 key
为null
,阐明此数据 key
值曾经被垃圾回收掉了,此时会执行 replaceStaleEntry()
办法,该办法含意是 替换过期数据的逻辑 ,以index=7
为终点开始向前遍历,进行探测式数据清理工作。
初始化探测式清理过期数据扫描的开始地位:slotToExpunge = stateSlot = 7
。
以以后 stateSlot
开始向前迭代找到,找到其余过期的数据,而后更新过期数据起始扫描下标的 slotToExpunge
,直到找到了Entry
为null
的槽位则完结。
如果找到过期数据,持续向前迭代,直到遇到 Entry=null
的槽位则进行迭代,如下图所示,slotToExpunge
被更新为 0:
上图以以后节点 index = 7
向前迭代,检测是否有过期的 Entry
数据,如果有则更新 slotToExpunge
的值,遇到 Entry
为null
则完结探测,以上图为例 slotToExpunge
被更新为 0。
下面向前迭代的操作是为了更新探测清理过期数据的起始地位 soltToExpunge
的值,这个值是用来判断以后过期槽位 staleSlot
之前是否还有过期元素。
接着开始 staleSolt
地位 index = 7
向后迭代,如果找到了相等 key
的Entry
的数据 则更新 value
值,如下图:
从以后节点 staleSolt
地位开始向后寻找 key
相等的 Entry
地位,如果找到了 key
相等的 Entry
,则会替换staleSlot
元素的地位,且更新 value
值,而后进行过期 Entry
的清理工作,如下图:
如果没有找到 相等 key
的Entry
的数据,如下图:
从以后节点 staleSlot
向后查找 key
值相等的 Entry
,如果没有找到,则会持续往后查找直到找到Entry
为null
进行,而后创立新的 Entry
,替换stableSlot
的地位。
替换实现之后也是进行过期元素的清理工作,清理工作的办法次要有两个 expungeStaleEntry
和cleanSomeSlots
,具体详情前面会讲到。
下面曾经图解了 set()
办法实现的原理,接下来咱们联合源码再来看看,源码如下:
private void set(ThreadLocal<?> key, Object value) {
// 获取 Entry 表
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();
// 如果查找到 key 相等的 entry,则更新 value
if (k == key) {
v.value = value;
return;
}
// 如果查找到为 key 为 null 的 entry,阐明 key 过期,被 GC 回收
// 这个时候要初始化探测式清理的起始地位
// 替换过期元素
if (k == null) {replaceStateEntry(key, value, i);
return;
}
}
// 循环查找过程中,没有找到 key 相等的 entry,且没有 key 过期的 entry
// 则新建一个 entry 放入 entry 表中
table[i] = new Entry(key, value);
// 寄存元素数量 +1
int sz = ++size;
// 调用启发式清理,且元素数量大于扩容阙值
// 则调用 rehash 办法,该办法会进行 key 过期的 entry 清理工作,清理实现之后再判断是否须要扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();}
下面代码流程次要如下:
- 首先获取
Entry
表,Entry
表长度,通过hashCode
计算下标,而后for
循环Entry
表。 - 如果循环查找过程中找到了
key
相等的Entry
则更新value
对应咱们下面说的 第二种状况。 - 如果循环查找过程找到了
key
为null
的Entry
,阐明key
过期了,替换过期元素,须要初始化探测式清理的其实地位,调用replaceStaleEntry()
办法,这个办法咱们上面再说,这个对应咱们下面说的 第四种状况。 for
循环查找结束,阐明在查找过程中该下标对应的Entry
为null
,则在新建一个Entry
放入该槽位,而后调用 启发式清理 工作。- 如果 启发式清理 未清理工作数据,且
size
超过扩容阙值 (2/3),则调用rehash()
办法,该办法会先进行一次探测式清理,清理过期元素,清理结束之后如果size >= threshold - threshold / 4
,则会进行扩容操作。
接下来看外围办法 replaceStaleEntry()
,该办法在查找过程中遇到key = null
数据的时候会执行,该办法提供了替换过期数据的性能,能够对应下面说 第四种状况 来看,源码如下:
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
// 获取 Entry 表
Entry[] tab = table;
// 获取 Entry 表长度
int len = tab.length;
Entry e;
// 定义探测式清理起始地位 slotToExpunge = staleSlot
int slotToExpunge = staleSlot;
// 从 staleSlot 开始向前迭代查找是否有 key=null 的 entry
// 如果有则更新 slotToExpunge
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
// staleSlot 开始向后循环
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();
// 如果查找到了 key 相等 entry
// 则替换 staleSlot 和 i 的地位,且更新 value 的值
if (k == key) {
e.value = value;
// 替换 staleSlot 和 i 的地位
tab[i] = tab[staleSlot];
// 更新 value 值
tab[staleSlot] = e;
// 如果 slotToExpunge == staleSlot,阐明向前循环的没有查找到 key 过期的 entry
// 更新 slotToExpunge 值
// 则会调用启动式过期清理,先会进行一遍过期元素探测操作
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
// 如果找到了 key 为 null 且向前循环的没有查找到 key 过期的 entry
// 则更新 slotToExpunge
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// 阐明没有找到 k == key 的数据,且碰到 Entry 为 null 的数据
// 则将数据放入该槽位
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
// slotToExpunge != staleSlot 阐明从 staleSlot 开始向前迭代查找有 key=null 的 entry
if (slotToExpunge != staleSlot)
// 启动式清理之前,先会进行一次过期元素探测,如果发现了有过期的数据就会先进行探测式清理
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
下面代码次要流程如下:
- 首先获取
Entry
表,Entry
表长度,定义探测式清理起始地位slotToExpunge = staleSlot
。 - 从 staleSlot 开始向前迭代查找是否有
key=null
的entry
,如果有则更新slotToExpunge
。 staleSlot
开始向后循环,如果查找到了key
相等entry
,则替换staleSlot
和i
的地位,且更新value
的值,而后判断slotToExpunge == staleSlot
,阐明向前循环的没有查找到key
过期的entry
,而后更新slotToExpunge
值,则会调用启动式过期清理,先会进行一遍过期元素探测操作,如果发现了有过期的数据就会先进行探测式清理。- 如果找到了
key
为null
且向前循环的没有查找到key
过期的entry
,则更新slotToExpunge
。 - 循环完结,办法没有退出,阐明没有找到
k == key
的数据,且碰到Entry=null
的数据,则将数据放入该槽位。 - 最初判断
slotToExpunge != staleSlot
,阐明从staleSlot
开始向前迭代查找有key=null
的entry
,则调用启动式清理,在启动式清理之前,先会进行一次过期元素探测,如果发现了有过期的数据就会先进行探测式清理。
ThreadLocalMap.set()
办法到这里曾经解析结束,咱们接下来看看 ThreadLocalMap
过期 key 的启发式清理流程。
ThreadLocalMap 过期 key 的启发式清理流程
下面咱们提到的 ThreadLocalMap
两种过期 key
数据清理形式:探测式清理 和启发式清理。
探测式清理
探测式清理办法 expungeStaleEntry
,遍历散列数组,从开始地位向后探测清理过期数据,将过期数据的Entry
设置为 null
,遍历过程如果遇到未过期的数据则会将此数据rehash
后从新在 table
数组中定位,如果定位的地位曾经有了元素,则会将未过期的数据放在最靠近此地位的 Entry = null
的桶中,使 rehash
后的 Entry
数据间隔正确的桶地位更近一点。这种优化会进步整个散列表查问性能。
如下图所示:
探测式清理迭代的过程中遇到了空的槽位,则终止探测,这样子一轮探测式清理就工作实现,咱们看看具体的源码实现,源码如下:
// staleSlot 探测式清理起始地位
private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;
int len = tab.length;
// 将起始地位置空
tab[staleSlot].value = null;
tab[staleSlot] = null;
// 元素数量减 1
size--;
// 从新迭代散列,直到发现空槽
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();
// 如果 key 过期,则清空元素,数量减 1
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
// 如果 key 没有过期,则从新计算 hash,从新获取下标
int h = k.threadLocalHashCode & (len - 1);
// 如果以后下标存在值,则寻找离抵触 key 所在 entry 最近的空槽
if (h != i) {
// i 地位槽置空
tab[i] = null;
// 寻找离抵触 key 所在 entry 最近的空槽,放入该槽
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
启发式清理
启发式清理被作者定义为:Heuristically scan some cells looking for stale entries
源码如下:
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {i = nextIndex(i, len); // 从下一个地位开始
Entry e = tab[i];
// 遍历到 key==null 的 Entry
if (e != null && e.get() == null) {
n = len; // 重置 n
removed = true; // 标记有清理元素
i = expungeStaleEntry(i); // 清理
}
} while ((n >>>= 1) != 0); // log(n) 限度 -- 对数次
return removed;
}
从 i
的下一个地位判断元素是否须要革除,如果遇到 key==null
的元素则会重置 n
,须要革除且更新i
的值,判断且革除结束之后,n = n >>> 1
直到 n = 0
则退出清理。
ThreadLocalMap.get()办法详解
下面曾经说完了 set()
办法的源码,接下来咱们看看 get()
办法的操作原理,次要蕴含两种状况,一种是 hash
计算出下标,该下标对应的 Entry.key
和咱们传入的 key
相等的状况,另外一种就是不相等的状况。
相等状况:相等状况解决很简略,间接返回value
,如下图:
上图中比方 get(ThreadLocal1)
计算下标为 4,且 4 存在 Entry
,且key
相等,则间接返回value = 11
。
不相等状况:不相等状况,先看图:
以 get(ThreadLocal2)
为例计算下标为 4,且 4 存在 Entry
,但key
相等,这个时候则为往后迭代寻找 key
相等的元素,如果寻找过程中发现了有 key = null
的元素则回进行探测式清理操作。如下图:
迭代到 index=5
的数据时,此时 Entry.key=null
,触发一次探测式数据回收操作,执行expungeStaleEntry()
办法,执行完后,index 5,8
的数据都会被回收,而 index 6,7
的数据都会前移,此时持续往后迭代,到 index = 6
的时候即找到了 key
值相等的 Entry
数据,如下图:
ThreadLocalMap.get()
源码如下:
public T get() {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;
return result;
}
}
// 未找到的话,则调用 setInitialValue()办法设置 null
return setInitialValue();}
private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
// key 相等间接返回
if (e != null && e.get() == key)
return e;
else
// key 不相等调用 getEntryAfterMiss()办法
return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {Entry[] tab = table;
int len = tab.length;
// 迭代往后查找 key 相等的 entry
while (e != null) {ThreadLocal<?> k = e.get();
if (k == key)
return e;
// 遇到 key=null 的 entry,先进行探测式清理工作
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
ThreadLocalMap 的扩容机制
在 ThreadLocalMap.set()
办法最初,如果执行完启发式清理工作之后,未清理任何数据,且以后散列数组中元素曾经超过扩容阙值 len*2/3
,则执行rehash()
逻辑:
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
rehash()
办法源码如下:
private void rehash() {
// 先进行探测式清理工作
expungeStaleEntries();
// 探测式清理结束之后 如果 size >= threshold - threshold / 4
// 也就是 size >= threshold * 3/4,也就是 size >= len * 1/2,则扩容
if (size >= threshold - threshold / 4)
resize();}
private void expungeStaleEntries() {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);
}
}
rehash()
办法源码流程如下:
- 首先进行探测式清理工作
- 如果探测式清理工作结束之后,如果
size >= threshold - threshold / 4
,也就是size >= threshold * 3/4
,也就是size >= len * 1/2
,则调用resize()
扩容。
扩容办法 resize()
办法源码如下:
private void resize() {Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {Entry e = oldTab[j];
if (e != null) {ThreadLocal<?> k = e.get();
if (k == null) {e.value = null;} 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;
}
扩容办法执行之后 tab
的大小为原先的两倍 oldLen * 2
,而后变量老的散列表,从新计算hash
地位,而后放到新的散列表中,如果呈现 hash
抵触则往后寻找最近的 entry
为null
的槽位放入,扩容实现之后,从新计算扩容阙值。
六、ThreadLocal.get()办法源码详解
ThreadLcoal.get()
办法源码详解曾经在 ThreadLocalMap.get()
办法源码解析中实现。
七、ThreadLocal.remove()办法源码详解
ThreadLocal.remove()
办法流程比较简单,咱们联合源码来阐明,源码如下:
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal<?> key) {Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
// 从 hash 获取的下标开始,寻找 key 相等的 entry 元素革除
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {if (e.get() == key) {e.clear();
expungeStaleEntry(i);
return;
}
}
}
ThreadLocal.remove()
外围是调用 ThreadLocalMap.remove()
办法,流程如下:
- 通过
hash
计算下标。 - 从散列表该下标开始往后查
key
相等的元素,如果找到则做革除操作,援用置为null
,GC
的时候key
就会置为null
,而后执行探测式清理解决。
八、InheritableThreadLocal
咱们在应用 ThreadLocal
的时候,在异步场景下是无奈给子线程共享父线程中创立的线程正本数据的。
为了解决这个问题,JDK
中还有一个 InheritableThreadLocal
类,咱们来看个例子:
public static void main(String[] args) {ThreadLocal<String> ThreadLocal = new ThreadLocal<>();
ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
ThreadLocal.set("父类数据:threadLocal");
inheritableThreadLocal.set("父类数据:inheritableThreadLocal");
new Thread(new Runnable() {
@Override
public void run() {System.out.println("子线程获取父类 ThreadLocal 数据:" + ThreadLocal.get());
System.out.println("子线程获取父类 inheritableThreadLocal 数据:" + inheritableThreadLocal.get());
}
}).start();}
下面代码输入后果为:
子线程获取父类 ThreadLocal 数据:null
子线程获取父类 inheritableThreadLocal 数据:父类数据:inheritableThreadLocal
实现原理是子线程通过父线程中调用 new Thread()
办法创立子线程,Thread#init
办法在 Thread
的构造方法中被调用,init()
办法中拷贝父线程数据源到子线程中,源码如下:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {if (name == null) {throw new NullPointerException("name cannot be null");
}
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
tid = nextThreadID();}
但 InheritableThreadLocal
依然有缺点,个别咱们做异步化解决都是应用的线程池,而 InheritableThreadLocal
是在 new Thread
中的 init()
办法给赋值的,而线程池是线程复用的逻辑,所以这里会存在问题。
当然,有问题呈现就会有解决问题的计划,阿里巴巴开源了一个 TransmittableThreadLocal
组件就能够解决这个问题,这里就不再延长,感兴趣的可自行查阅材料。
参考:https://javaguide.cn/java/con…