ThreadLocal
结构图
"ThreadLocal
的实现是这样的:每个Thread
维护一个 ThreadLocalMap
映射表,这个映射表的 key
是 ThreadLocal
实例本身,value
是真正需要存储的 Object
。
也就是说 ThreadLocal
本身并不存储值,它只是作为一个 key
来让线程从 ThreadLocalMap
获取 value
。
Thread线程内部的 ThreadLocalMap
在类中描述如下:
1 | public class Thread implements Runnable { |
源码分析
set()方法
1 | /** |
步骤:
- 根据当前线程获取到ThreadLocalMap(如果ThreadLocalMap不存在,就创建一个实例)
- 以当前threadlocal对象为key保存到ThreadLocalMap中
get()方法
1 | /** |
步骤:
- 根据当前线程获取到ThreadLocalMap(如果ThreadLocalMap不存在,就创建一个实例)
- 从ThreadLocalMap中获取当前thradlocal对象对应的value
remove()方法
1 | /** |
步骤:
- 略。
ThreadLocalMap
ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,用独立的方式实现了Map的功能,其内部的Entry也独立实现。
"在ThreadLocalMap中,也是用Entry来保存K-V结构数据的。但是Entry中key只能是ThreadLocal对象,这点被Entry的构造方法已经限定死了。
1 | static class Entry extends WeakReference<ThreadLocal> { |
Entry继承自WeakReference(弱引用,生命周期只能存活到下次GC前),但只有Key是弱引用类型的,Value并非弱引用。
Hash冲突怎么解决
ThreadLocalMap中解决Hash冲突的方式是采用线性探测的方式,所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。
ThreadLocalMap解决Hash冲突的方式就是简单的步长加1或减1,寻找下一个相邻的位置。
1 | private void set(ThreadLocal<?> key, Object value) { |
显然ThreadLocalMap采用线性探测的方式解决Hash冲突的效率很低,如果有大量不同的ThreadLocal对象放入map中时发送冲突,或者发生二次冲突,则效率很低。
为什么使用弱引用
分两种情况讨论:
- key 使用强引用:引用的
ThreadLocal
的对象被回收了,但是ThreadLocalMap
还持有ThreadLocal
的强引用,如果没有手动删除,ThreadLocal
不会被回收,导致Entry
内存泄漏。 - key 使用弱引用:引用的
ThreadLocal
的对象被回收了,由于ThreadLocalMap
持有ThreadLocal
的弱引用,即使没有手动删除,ThreadLocal
也会被回收。value
在下一次ThreadLocalMap
调用set
,get
,remove
的时候会被清除。
比较两种情况,我们可以发现:由于ThreadLocalMap
的生命周期跟Thread
一样长,如果都没有手动删除对应key
,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
get方法
在get的过程中,如果遇到某槽位满足一下条件:
table[i]!=null && table[i].get()==null
,就清理该槽位expungeStaleEntry(i)
1 | private Entry getEntry(ThreadLocal<?> key) { |
remove方法
在remove的过程中,如果遇到某槽位满足一下条件:
table[i]!=null && table[i].get()==null
,就清理该槽位expungeStaleEntry(i)
1
2
3
4
5
6
7
8
9
10
11
12
13
14private void remove(ThreadLocal<?> key) {
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)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}set方法
在set的过程中:
如果遇到某槽位满足一下条件:
table[i]!=null && table[i].get()==null
,就替换掉该槽位的值replaceStaleEntry(i)
在找到一个空的槽位,把(key,value)放在该槽位里面后,后触发
cleanSomeSlots
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15private 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];
if (e != null && e.get() == null) {
n = len;
removed = true;
i = expungeStaleEntry(i);
}
} while ( (n >>>= 1) != 0);
return removed;
}
因此,ThreadLocal
内存泄漏的根源是:由于ThreadLocalMap
的生命周期跟Thread
一样长,如果没有手动删除对应key
就会导致内存泄漏,而不是因为弱引用。
InheritableThreadLocal
源码
1 | public class InheritableThreadLocal<T> extends ThreadLocal<T> { |
从上述源码可知:InheritableThreadLocal对于的ThreadLocalMap是放在thread.inheritableThreadLocals
属性中的,与ThreadLocal不同。
拷贝实现
ThreadLocal的拷贝发生在:当前线程生成子线程实例的时候。如果当前线程的inheritableThreadLocals属性不为空,就会把该属性拷贝到子线程的inheritableThreadLocals属性中
1 | public Thread() { |
1 | static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { |
TransmittableThreadLocal
概述
JDK
的InheritableThreadLocal
类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的ThreadLocal
值传递已经没有意义,应用需要的实际上是把 任务提交给线程池时的ThreadLocal
值传递到 任务执行时。
TransmittableThreadLocal
类继承并加强InheritableThreadLocal
类,解决上述的问题。
相比InheritableThreadLocal
,添加了
protected
方法copy
用于定制 任务提交给线程池时 的ThreadLocal
值传递到 任务执行时 的拷贝行为,缺省传递的是引用。protected
方法beforeExecute
/afterExecute
执行任务(Runnable
/Callable
)的前/后的生命周期回调,缺省是空操作。
整个过程的完整时序图
"demo:保证线程池中传递值
1 | public class Demo { |
拷贝实现
"TransmittableThreadLocal 覆盖实现了 ThreadLocal 的 set、get、remove,实际存储 ThreadLocal 值的工作还是 ThreadLocal 父类完成,TransmittableThreadLocal 只是为每个使用它的 Thread 单独记录一份存储了哪些 TransmittableThreadLocal 对象。拿 set 来说就是这个样子:
1 | public final void set(T value) { |
TtlRunnable在装饰Runnable的时候,会把TransmittableThreadLocal.holder
中的threadlocal以及其对于的value拷贝到capturedRef
属性中,在子线程调用run方法的时候,把capturedRef
复制到子线程中的threadlocal中。
1 | public final class TtlRunnable implements Runnable, TtlEnhanced { |