ThreadLocal
是什么
ThreadLocal
是一个泛型类,用于在线程中定义局部变量。官方的解释为:这个类提供线程的局部变量。 这些变量与其正常的对应方式不同,因为访问每个线程(通过其 get
或 set
方法)都有自己独立初始化的变量副本
ThreadLocal
的重要说明
ThreadLocalMap
说明
ThreadLocalMap
是ThreadLocal
类中的一个内部类(本类和同包的类可以访问)
ThreadLocalMap
内部定义了弱引用的数组Entry
用来存放key
和value
,Entry
数组的默认容量为16
,并且数组的容量必须为2
的次幂
// ThreadLocalMap内部声明的Entry,用于存放key和value,key为ThreadLocal类型的弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// 内部Entry数组的默认初始容量为16,数组的容量必须为2的幂
// 由于ThreadLocalMap内部有很多(容量-1)的与运算(&)计算数组索引,
// 容量为2的幂可以保证(容量-1)转化为二进制后最后一位始终是1,这样可以有效的减少碰撞几率
private static final int INITIAL_CAPACITY = 16;
// 内部Entry数组
private Entry[] table;
// 数组中存放的元素数量
private int size = 0;
// 数组扩容临界点
private int threshold;
ThreadLocal
存值:
- 通过
Thread.currentThread()
先获取到当前线程对象。(由于Thread
里面的ThreadLocalMap
变量对象是默认修饰符修饰,而ThreadLocal
与Thread
位于相同的包中,所以在ThrealLocal
类中可以通过Thread.currentThread()
获取到当前线程对象后,直接通过Thread
对象可以访问到该线程的ThreadLocalMap
对象) - 再通过
ThreadLocalMap
的set
方法,key
为当前ThreadLocal
的对象,value
为想设置的值,说得直白一点,ThreadLocal
存储值,是通过线程自己内部的ThreadLocalMap
存储的 - 而不同的线程自然就有不同的
ThreadLocalMap
对象变量。如果在同一个线程中声明了两个ThreadLocal
对象,而在Thread
内部都是使用仅有那个ThreadLocalMap
存放数据的
ThreadLocal
取值
ThreadLocal
的get
方法获取值,就是通过获取到当前线程的对象,然后通过当前线程获取到该线程的ThreadLocalMap
对象,最后通过ThreadLocalMap
拿到最开始设置的值
ThreadLocal
的set
方法源码
/**
* ThreadLocalMap 是 ThreadLocal 的一个默认访问修饰符修饰的内部类,也就是说 ThreadLocalMap 可以在 ThreadLocal 内部和同一个包的其他类中使用
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
// 调用ThreadLocalMap类的set方法
map.set(this, value);
else
// 调用ThreadLocal类的createMap方法
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
// 返回当前线程的内部参数threadLocals,线程对象中threadLocals参数,只要你用不到ThreadLocal,线程对象的这个属性就一直是null
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
// 将当前线程对象作为key,新生成一个ThreadLocalMap对象,并将线程的threadLocals对象设置为这个新生成的ThreadLocalMap
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
- 如果
getMap
不是空,就用ThreadLocalMap
的set
方法置入一个以当前线程对象的键,value
为值的这么一个键值对 - 如果
getMap
为空,那么就以createMap
方法set
第一个值
ThreadLocalMap
类的set
方法
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();
}
- 计算当前键的
hash
值 - 去
table
里找,重复键就替换值,不重复就在该位置添加这个键值对 - 当前容量超过阈值就扩容然后
rehash()
ThreadLocal
类的createMap
方法
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
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);
}
createMap
方法的意思就是,构造一个新的ThreadLocalMap
对象,将value
对象塞进map
,然后把传入的线程对象的threadLocals
属性指向这个新ThreadLocalMap
ThreadLocal
的get
方法源码
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
当我们在调用get()
方法的时候,先获取当前线程对象,然后获取到当前线程的ThreadLocalMap
对象
- 如果非空,那么取出
ThreadLocal
的value
- 否则进行初始化,初始化就是将
initialValue
的值set
到ThreadLocal
中
ThreadLocal
的set
和 get
小结
ThreadLocal
进行set
的时候,是在当前线程Thread
中获取到有且唯一的ThreadLocalMap
对象(如果没有就新建一个ThreadLocalMap
对象设置进Thread
的属性里),然后把自己作为键,value
作为值set
进这个Map
里ThreadLocal
进行get
的时候,是从当前线程Thread
中获取到有且唯一的ThreadLocalMap
对象(Thread
的ThreadLocalMap
属性如果为空,也就是说这个线程从来都没有用过ThreadLocal
设置过值,返回null
),然后把自己做为键去该Map
里面找,找到就返回对于的value
,没有就返回null
使用 ThreadLocal
的注意点
内存泄漏
ThreadLocalMap
使用ThreadLocal
的弱引用作为key
,如果一个ThreadLocal
没有外部强引用来引用它,那么系统 GC
的时候,这个ThreadLocal
势必会被回收,ThreadLocalMap
中就会出现key
为null
的Entry
,就没有办法访问这些key
为null
的Entry
的value
,如果当前线程再迟迟不结束的话,这些key
为null
的Entry
的value
就会一直存在一条强引用链:Thread Ref
-> Thread
-> ThreaLocalMap
-> Entry
-> value
永远无法回收,造成内存泄漏
ThreadLocalMap
的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal
的get(),set(),remove()
的时候都会清除线程ThreadLocalMap
里所有key
为null
的value
为什么用弱引用
key
使用强引用:引用的ThreadLocal
的对象被回收了,但是ThreadLocalMap
还持有ThreadLocal
的强引用,如果没有手动删除,ThreadLocal
不会被回收,导致Entry
内存泄漏。
key
使用弱引用:引用的ThreadLocal
的对象被回收了,由于ThreadLocalMap
持有ThreadLocal
的弱引用,即使没有手动删除,ThreadLocal
也会被回收。ThreadLocalMap
在调用set、get、remove
的时候会清除Entry
数组中所有key
为空的Entry
,这样就避免了因为value
没被回收而造成内存溢出。
由于ThreadLocalMap
的生命周期跟Thread
一样长,如果没有手动删除对应key
,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal
不会内存泄漏,对应的value
在下一次ThreadLocalMap
调用set,get,remove
的时候会被清除