是ThreadLocal是web中线程中存储变量 传送数据的神奇,因为web中每个请求都一个是线程,所以可以理解为一次请求到结束都是可以在ThreadLocal中存储 获取的(不包括异步、响应式webflux),因为他们整个请求不是在同一线程
#ThreadLocal那么原理是什么#
在Thread里有个threadLocals 字段,类型为ThreadLocal.ThreadLocalMap
ThreadLocalMap为一个手写map,里面有个数组 table,类型为ThreadLocal.ThreadLocalMap.
Entry
这个Entry是内在释放关键,晚点说
线程启动后会有个字段threadLocals存储信息,那么和 ThreadLocal有什么关系?
其实这里设计有点怪,也许才是精髓
ThreadLocal有两层作用
1 更像是个工具类,类里方法去控制调用 Thread中的 threadLocals字段的
2 是做为ThreadLocalMap中的一个key, 我们知道map都是由key和value组成
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
所以说有点怪,做为一个类利用自己为key 把自己存在别的类里
平时我们实现可能都会分开,再用一个静态类,或别的管理类来做这个事情
如写一个 ThreadLocalStore 里面实现
public class ThreadLocalStore {
public static void set(ThreadLocal key,T value ) {
Thread t = Thread.currentThread();
ThreadLocal.ThreadLocalMap map = t.threadLocals;
if (map != null)
map.set(key, value);
else
createMap(t,key, value);
}
static void createMap(Thread t,ThreadLocal key, T firstValue) {
t.threadLocals = new ThreadLocal.ThreadLocalMap(key, firstValue);
}
public T get(ThreadLocal key) {
Thread t = Thread.currentThread();
ThreadLocal.ThreadLocalMap map = t.threadLocals;
if (map != null) {
ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(key);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue(key);
}
private T setInitialValue(ThreadLocal key) {
Thread t = Thread.currentThread();
ThreadLocal.ThreadLocalMap map = t.threadLocals;
createMap(t, key,null);
return null;
}
}
上面这个是仿照ThreadLocal原来set get写的
ThreadLocal threadLocal= new ThreadLocal();
使用时就是
ThreadLocalStore.set(threadLocal,"test")); ThreadLocalStore.get(threadLocal);
可能这样更像我们平时写代码的设计,按这样看,threadLocal的原理就很简单了吧
下文说的GC,包括youngGc和FullGc
现在我们看一下ThreadLocalMap.Entry
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
继承了WeakReference 弱引用
弱引用应该都知道,如果发生了GC会回收弱引用,但有一点要注意,就是这个弱引用并不是说发生GC就会被回收,还要满足这个引用的对象没有再被强引用时才会
public void test(){
WeakReference
比如上面的代码里,obj被获取出来了后,这个obj如果被一直传递或使用时,就算GC,myWeakReference弱引用里的Object就不会被收回。 所以a点可以被回收,c点不行
public void test2(){
WeakReference myWeakReference = new WeakReference(new Object());
System.out.println(myWeakReference.get());
}
上面的代码仅get了弱引用,没有再赋值,只要发生GC 就会被回收
所以弱引用的对象有没有被再赋值 强引用很关键!
public void test3(){
WeakReference myWeakReference = new WeakReference(new Object());
//a点
childTest(myWeakReference);
//b点
}
public void childTest(WeakReference myWeakReference){
//c点
Object obj=myWeakReference.get();
//d点
System.out.println(obj);
//d点
}
上面的代码a点,b点,c点发生GC都被回收弱引用的对象,但只要childTest内d点以后时发生GC都不会回收
因为childTest里赋值 强引用了obj,只有等到childTest方法执行完 obj作用域完没有被引用时,发生GC才会回收弱引用
当然弱引用里还有个clear方法,这个意义就是把我们new的弱引用对象和 引用对象本身关系解开,就是引用的对象生命周期还是他原来的周期,和弱引用没有任何关系了,该是什么还是什么
如果这些你明白了,再来看ThreadLocalMap里的Entry类 key是被弱引用包裹,value正常字段
static class Entry extends WeakReference> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal> k, Object v) {
super(k);
value = v;
}
}
所以value和弱引用什么的就没什么关系 只是一个普通字段,只要Entry对象不回收value永远也不回收
key的话就是你创建的ThreadLocal对象,如果没有在别的地方有强引用了,GC是Key就会被回收,也就是你new 的ThreadLocal对象,发生GC随时都可能被回收掉
那么key随时被回收,为什么我们代码里从不会发生 Gc后 ThreadLocal取不到值的情况,刚才认真看的人应该已经知道了, 因为ThreadLocal是我们创建的,一直存在强引用,,如果你创建的是静态的 那么程序里永远也不会回收掉
static ThreadLocal
又绕回来,如果你的ThreadLocal在ThreadLocalMap.Entry的弱引用回收了,那说明ThreadLocal已经不存在任何引用,作用域已经结果了,你自己肯定也取不到创建的ThreadLocal对象了
public void test(){
test3();
//a
}
public void test3(){
ThreadLocal threadLocal = new ThreadLocal();
threadLocal.set(new Object());
}
在test3执行完完后a点你的threadLocal可能被回收了,但在a点你获取不到threadLocal了呀, 如果获取到了那threadLocal就是有强引用的 也不可能被回收
理论上当ThreadLocal生命周期结束后,你存进去的value也没有什么用了应该被回收,因为你没有key,永远也取不到value了,但现实不是。就算发生GC后 存进去的Object有时还会存在,猜测是个bug
看看GC发生了什么
当执行GC完后 如果ThreadLocal没有被强引用,那么Thread内的map(也就是threadLocals字段ThreadLocalMap类型) 内的值Entry 的key会被回收,但Entry不会被回收 value也不会被回收,Entry是在map内的值,map不移除它引用就在。那什么情况被回收,下面前题是value本身没有被引用
1 如果你调用了ThreadLocal.remove()方法,当remove后 value被map移除了,自然没有引用可回收
2 线程销毁结束(线程池里的线程一般是不销毁的,所以正常是不会回收value),线程结束会把
threadLocals=null,那么map内所有数据都没有引用,可被回收
3 gc后,Thread内的map内的值Entry 的key会被回收, map本身set, remove时
会调用到expungeStaleEntry方法,删除Entry里key为空的,此时value也没有引用 可被回收
但是3里 set 时判断map有扩容时才会执行expungeStaleEntry,所以value偶尔被回收,偶尔不会,而且这个判断扩容好像有问题(没有深入研究,也可能是GC是异步的导致map大小异常)导致后面就算有扩容也有部分对象永远不回收
总结:
如果你是new 的ThreadLocal那么就记得remove ,尤其在线程池和web程序里,因为线程不会销毁ThreadLocal只能靠同一线程里下次设置TheadLocal,set时才可能清理
尽量创建static的ThreadLocal,这样保证一个线程就一个TreadLocal,就算不remove基本也不会引起内存泄露,因为ThreadLocal和线程数一样多