一. 先来理解几个概念
* 什么是 ThreadLocal
ThreadLocal 叫做线程变量,意思是 ThreadLocal 中填充的变量属于以后线程,该变量对其余线程而言是隔离的。ThreadLocal 为变量在每个线程中都创立了一个正本,那么每个线程能够拜访本人外部的正本变量。
* ThreadLocal 源码解析
如图次要的几个办法,上面逐个剖析:
public void set(T value) {Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {return t.threadLocals;}
首先获取到了以后线程 t,而后调用 getMap 获取 ThreadLocalMap,源码能够看出获取的是以后线程保护的一个成员变量,如果 map 存在,则将 ThreadLocal 对象作为 key,要存储的对象作为 value 存到 map 外面去。如果该 Map 不存在,则初始化一个。
ThreadLocalMap 其实就是 ThreadLocal 的一个动态外部类,外面定义了一个 Entry 来保留数据。在 Entry 外部应用 ThreadLocal 作为 key,应用咱们设置的 value 作为 value。
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;
} } return setInitialValue();}
首先获取以后线程,而后调用 getMap 办法获取一个 ThreadLocalMap,此对象为以后线程 Thread 保护的一个变量,如果 map 不为 null,那就应用以后线程作为 ThreadLocalMap 的 Entry 的键,而后值就作为相应的的值,如果没有那就设置一个初始值。
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;
}
setInitialValue 办法外面调用了 createMap 办法
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}
这里就是每个线程当中的成员变量的初始化操作。
* 总结
(1)每个 Thread 保护着一个 ThreadLocalMap 的援用
(2)ThreadLocalMap 是 ThreadLocal 的外部类,用 Entry 来进行存储
(3)ThreadLocalMap 的键值为 ThreadLocal 对象,而且能够有多个 threadLocal 变量,因而保留在 map 中
(4)ThreadLocal 自身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。
二. ThreadLocal 的应用以及留神
* java 的四种援用阐明
1.StrongRefrence: 强援用,任何时候都不会被 GC 回收;
2.SoftRefrence: 软援用,内存不足时会被 GC 回收,内存足够的时候,软援用对象不会被回收,只有在内存不足时,零碎则会回收软援用对象,如果回收了软援用对象之后依然没有足够的内存,才会抛出内存溢出异样;
3.WeakRefrecen: 若援用,GC 时会被回收;
4.PhantomRefrece: 虚援用,虚援用存在的惟一作用就是当它指向的对象被回收后,虚援用自身会被退出到援用队列中,用作记录它指向的对象已被销毁。
对象可达性判断:以后支流 java 虚拟机都是采纳 GC Roots Tracing 算法,java 虚拟机进行 gc 时,判断一个对象的被援用状况决定是否回收,都是从根节点援用(Root set of Reference)开始标识可达门路的:
Root Tracing 算法依据以下两个准则标记对象的可达性:
繁多门路中,以最弱的援用为准
多路径中,以最强的援用为准
一个例子:
MyObject aRef = new MyObject();
SoftReference aSoftRef= new SoftReference(aRef);
对于这个 MyObject 对象,有两个援用门路,一个是来自 SoftReference 对象的软援用,一个来自变量 aReference 的强援用,所以这个 MyObject 对象是强可及对象。
aRef = null;
这个 MyObject 对象成为了软可及对象。如果垃圾收集线程进行内存垃圾收集,内存不断,并不会因为有一个 SoftReference 对该对象的援用而始终保留该对象。垃圾收集线程会在虚拟机抛出 OutOfMemoryError 之前回收软可及对象,而且虚构机会尽可能优先回收长时间闲置不用的软可及对象,对那些刚刚构建的或刚刚应用过的“新”软可拥护象会被虚拟机尽可能保留
* ThreadLocal 应用的内存透露问题
上述源码,咱们得悉,ThreadLocalMap 里得 key 保留得是 ThreadLocal 对象得弱援用
上图看出:
1、Thread 中有一个 map,就是 ThreadLocalMap
2、ThreadLocalMap 的 key 是 ThreadLocal,值是咱们本人设定的。
3、ThreadLocal 是一个弱援用,会被当成垃圾回收
因为 ThreadLocalMap 的 Key 是弱援用的,在 GC 时会回收掉。当线程的生命周期大于 ThreadLocal 的生命周期时(大部分状况都是的,因为线程通过线程池治理会反复利用),那么就可能存在 ThreadLocalMap<null, Object>
的状况,这个 Object 就是透露的对象。
调用 remove 办法即可防止这个问题。
三. 总结以及发问
* ThreadLocal 内存透露得根本原因
因为 ThreadLocalMap 的生命周期跟 Thread 一样长,如果没有手动删除对应 key 就会导致内存透露。
* ThreadLocal 得应用场景
保护调用链路的 requestID:
Mybatis 插件 PageHelper
等等
* ThreadLocalMap 里得 key 保留得是 ThreadLocal 对象为什么不是强援用
ThreadLocalMap 的生命周期跟 Thread 一样长,即便咱们应用完结,也会因为线程自身存在该对象的援用,处于对象可达状态,垃圾回收器无奈回收。这个时候当 ThreadLocal
太多的时候就会呈现内存透露的问题。
* ThreadLocalMap 里的 value 为啥不必弱援用而要用强援用
如果 vaule 是弱援用,可能 value 被删除了,然而 Key 还存在另外的强援用,呈现为 null 的状况