什么是ThreadLocal
?
称为
线程本地变量
,当使用ThreadLocal
维护变量时,每个Thread
拥有一份自己的变量副本,多个线程之间互不干扰,实现线程间的数据隔离
ThreadLocal
维护的变量在线程的生命周期内起作用
ThreadLocal
数据结构
- 每个线程对应一个
Thread
对象,每个Thread
对象中,都拥有一个ThreadLocal.ThreadLocalMap
成员变量 ThreadLocalMap
类似于HashMap
,维护的都是key-value
键值对(HashMap
维护的是数组+链表/红黑树;ThreadLocalMap
维护的是数组)ThreadLocalMap
数组中存放的是静态内部类对象Entry(ThreadLocal<?> key, Object value)
数组
(key
实际上是弱引用WeakReference<ThreadLocal<?>>
证明:ThreadLocalMap
中的key
为什么要用弱引用?)
Java
的四种引用类型
参考强引用、软引用、弱引用、虚引用
ThreadLocalMap
中的key
为什么要用弱引用?
// 证明 key 为弱引用
// 有强引用指向ThreadLocal对象
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("abc");
// 没有强引用指向ThreadLocal对象
new ThreadLocal<>().set("def");
// 手动垃圾回收
System.gc();
// 垃圾回收后,def 对应的 key=null
// 没有强引用时,弱引用指向的对象会被垃圾回收器回收
- 当
ThreadLocal
没有强引用指向时,弱引用的key
会被gc
回收 value
只有在线程生命周期结束或触发清理算法时才会被gc
回收
ThreadLocal.set()
方法
private void set(ThreadLocal<?> key, Object value) {
ThreadLocal.ThreadLocalMap.Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
// 开放定址法查找可用的槽位(用于解决HASH冲突)
for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
// 如果槽位上已经有值,并且key相同,则替换value值
if (k == key) {
e.value = value;
return;
}
// 如果槽位上有值,并且key已经被GC回收了,触发探测式清理,清理掉过时的条目
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 没有对应槽位,将key和value插入新槽位
tab[i] = new ThreadLocal.ThreadLocalMap.Entry(key, value);
int sz = ++size;
// 触发清理,并判断如果清理后的size达到了阈值,则进行rehash进行扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
HashMap
解决Hash
冲突的方式是链表/红黑树ThreadLocalMap
中用的是**开放定址法
**
0x61c88647
- 每创建一个
ThreadLocal
对象,hashCode
增量都是这个数值 - 是
Integer
有符号整数的0.618
倍,即黄金比例,斐波那契数列 - 使
hash
分布非常均匀
ThreadLocalMap
扩容
private void rehash() {
// 清理过时条目,也就是key被GC回收掉的条目
expungeStaleEntries();
// Use lower threshold for doubling to avoid hysteresis
// 使用较低的阈值以避免迟滞
if (size >= threshold - threshold / 4)
// 容量变为原来的两倍
// Double the capacity of the table
resize();
}
ThreadLocal.get()
方法
public T get() {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 从线程对象t中获取ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null) {
// 通过key(当前ThreadLocal对象)寻找value
// 遵循开放定址法原则
// 没有查找继续向后查找
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果获取不到值,则初始化一个值
return setInitialValue();
}
- 如果
map
不为null
,以当前threadLocal
为key
进行查询,查询规则遵循开放定址法
原则 - 如果当前
hash
计算出的位置没有查到,继续向后查找 - 如果最终还是没有查到结果,初始化一个写入并返回
InheritableThreadLocal
简单介绍
使用ThreadLocal
时,子线程获取不到父线程通过set
方法保存的数据,要想使子线程也可以获取到,可以使用InheritableThreadLocal
类
原理
-
线程类
Thread
中有两个ThreadLocal.ThreadLocalMap
成员变量,一个存放普通ThreadLocal
相关信息,一个存放InheritableThreadLocal
相关信息// 用来保存ThreadLocal相关信息 ThreadLocal.ThreadLocalMap threadLocals = null; // 用来保存InheritableThreadLocal相关信息 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
-
InheritableThreadLocal
对象在调用set
方法保存信息时,调用的是父类ThreadLocal
对象的set
方法 -
InheritableThreadLocal
重写了getMap
和createMap
-
new Thread()
时复制一份当前线程的inheritableThreadLocals
到要创建的子线程中// 当前线程就是新建线程的父线程 Thread parent = currentThread(); if (inheritThreadLocals && parent.inheritableThreadLocals != null) // 复制当前线程的inheritableThreadLocals个子线程 this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
注意点
- 一般我们做异步化处理都是使用的线程池,
InheritableThreadLocal
是在new Thread
中的init()
方法给赋值的,而线程池是线程复用的逻辑,所以这里会存在问题 - 要想在使用线程池等会缓存线程的组件情况下传递
ThreadLocal
到子线程中,可以使用阿里巴巴开源组件TransmittableThreadLocal
- 子线程中数据是从父线程拷贝来的,所以,在子线程中重新set的内容,对于父线程是不可见的
应用
- 管理数据库连接
ThreadLocal
使用注意事项
-
内存泄漏或脏数据
- 通过线程池管理线程,一般使用完成后并不会进行销毁,若
ThreadLocal
也没有执行remove()
方法,会导致数据一直存在,造成内存泄漏; - 如果此时
ThreadLocal
也是一个静态常量,下一次使用线程时,很可能能够获取到之前保存的数据,导致脏数据 - 使用
ThreadLocal
时,一定要在最后调用remove()
- 通过线程池管理线程,一般使用完成后并不会进行销毁,若
参考资料: