ThreadLocal的API
- set(Object object);
- get();
- remove();
ThreadLocal的基本概念
- ThreadLocal是每条线程独享,在调用ThreadLocal对象的set()方法时,源码会创建一个ThreadLocalMap的集合,其中Key =this,即ThreadLocal对象本身。在set(), get(), remove()这些api被调用时,都会获取当前线程,判断下当前线程的ThreadLocalMap对象是否存在,如果不存在,则创建。存在则直接赋值,那么get()就是存在返回对应值,不存在就返回null,因为value默认值是null
源码:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocal的应用场景
- Spring的事务模板类
- 获取HttpsServletRequest
- AOP 或 拦截器中存放一些缓存数据
为什么每个线程缓存的是ThreadLocalMap对象,而不是ThreadLocal
- 每个ThreadLocalMap可以存放多个ThreadLocal
- 每个ThreadLocal只能存放一个数据
- 每个线程会有一个ThreadLocalMap,但ThreadLocal在一个线程中可以有多个,如果一个不够用,就可以再创建一个
强,软,弱,需引用的定义
- 强引用: 只要还存在引用关系,无论如何都不会被回收
- 软引用: 如果内存空间足够,则不会回收;如果内存空间不足,即使存在引用关系也会被回收
- 弱引用: 无论内存是否充足,无论是否存在引用关系,只要进行了gc,就会被回收==(ThreadLocal采用的方式)==
- 虚引用: 形同虚设,基本不用
内存泄漏 与 内存溢出的区别
- 内存泄漏: 申请的内存,无法得到释放,就是内存泄漏
- 内存溢出: 申请内存是,没有足够大连续空间的内存可申请,导致OOM异常,就是内存溢出(剩余总内存可能够,但连续内存空间不足)
ThreadLocal的弱引用内存泄露问题
========会出现内存泄漏的代码
ThreadLocal<String> threadLocal1 = new ThreadLocal();
// 一定要调用set方法,因为调用set才会把ThreadLocal对象放到ThreadLocalMap中
threadLocal1.set("testThreadLocal");
threadLocal1 = null;
上面代码片段,虽然赋值null,但是ThreadLocalMap依然和new ThreadLocal建立引用关系的,虽然ThreadLocalMap的Key存的指针已经是null了,但引用关系还在,所以无法被清理,最后就是内存泄漏
========不会出现内存泄漏的代码1
ThreadLocal<String> threadLocal1 = new ThreadLocal();
// 一定要调用set方法,因为调用set才会把ThreadLocal对象放到ThreadLocalMap中
threadLocal1.set("testThreadLocal");
threadLocal1.remove();
threadLocal1 = null;
调用一次remove方法即可
========不会出现内存泄漏的代码2
ThreadLocal<String> threadLocal1 = new ThreadLocal();
// 一定要调用set方法,因为调用set才会把ThreadLocal对象放到ThreadLocalMap中
threadLocal1.set("testThreadLocal");
threadLocal1 = null;
ThreadLocal<String> threadLocal2 = new ThreadLocal();
threadLocal2 .set("testThreadLocal2");
或者在后面再创建一个ThreadLocal对象,并调用set方法也可以。
原因:因为同一个线程的2个ThreadLocal对象会存放到同一个ThreadLocalMap中。而set()方法源码的处理方式是:每set一次,都会遍历ThreadLocalMap的Entry中所有数据,当有null时,会自动帮忙清空,所以这样可以避免内存泄漏
========set方法帮忙清理的代码片段
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
// 这里做了null判断处理
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
ThreadLocal为什么弱引用还会内存泄漏?
- 弱引用在源码是ThreadLocalMap中的Entry继承了WeakReference<ThreadLocal<?>>, 弱引用再没有进行gc的时候,依然是存在的,是占用内存的。
- Entry在ThreadLocalMap中的,而ThreadLocalMap的生命周期是和当前线程一致的,所以线程不结束,ThreadLocalMap不会被回收。
- (强制)不要再全局创建ThreadLocal
- (推荐)使用完后,调用下remove()方法