前言
上次有个小伙伴问我,说他面试的时候,被问到ThreadLocal内存溢出问题,没有回答出来;那我们今天就来了解一下ThreadLocal。
ThreadLocal介绍
多线程在访问同一个变量时会产生线程安全问题,ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题,如下图所示
ThreadLocal使用
开启两个线程,在每个线程内部设置了本地变量的值,然后调用print方法打印当前本地变量的值。如果在打印之后调用本地变量的remove方法会删除本地内存中的变量,代码如下所示
public class ThreadLocalTestDemo {
static ThreadLocal<String> localVar = new ThreadLocal();
static void print(String str) {
//打印当前线程中本地内存中本地变量的值
System.out.println(str + " :" + localVar.get());
/清除本地内存中的本地变量
localVar.remove();
}
public static void main(string[] args) {
Thread t1 =new Thread(new Runnable() {
aoverride
public void run() {
//设置线程1中本地变量的值
localVar.set( "localVar1");
//调用打印方法
print( "thread1");
//打印本地变量
System.out.println("thread1 after remove : " + localVar.get());
}
});
Thread t2 =new Thread( new Runnable() {
@0verride
public void run() {
//设置线程2中本地变量的值
localvar.set( "localVar2" );
//调用打印方法
print("thread2");
//打印本地变量
System.out.println("thread2 after remove : " + localVar.get());
}
});
t1.start();
t2.start();
}
}
执行结果
thread1 :localVar1
thread2 :localVar2
thread1 after remove : null
thread2 after remove : null
上面代码能够看出,虽然2个线程都使用了ThreadLocal localVar变量,但是线程里面的变量互相不影响。
ThreadLocal原理
我们来看看ThreadLocal是怎么实现的?我们来看一下源码
set源码
public void set(T value) {
//(1)获取当前线程(调用者线程)
Thread t = Thread.currentThread();
//(2)以当前线程作为key值,去查找对应的线程变量,找到对应的map
ThreadLocalMap map = getMap(t);
//(3)如果map不为nuil,就直接添加本地变量,key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值
if (map != null)
map.set(this, value);
//(4)如果map为nuli,说明首次添加,需要首先创建出对应的map
else
createMap(t, value);
}
在上面的代码中,(2)处调用getMap方法获得当前线程对应的threadLocals,该方法代码如下
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; //获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上
}
如果调用getMap方法返回值不为null,就直接将value值设置到threadLocals中(key为当前ThreadLocal引用,值为本地变量);如果getMap方法返回null说明是第一次调用set方法(前面说到过,threadLocals默认值为null,只有调用set方法的时候才会创建map),这个时候就需要调用createMap方法创建threadLocals,该方法如下所示
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
createMap方法不仅创建了threadLocals,同时也将要添加的本地变量值添加到了threadLocals中。
get源码
public T get() {
//(1)获取当前线程
Thread t = Thread. currentThread();
//(2)获取当前线程的threadLocals变量
ThreadLocalMap map = getMap(t);
//(3)如果threadLocals变量不为null,就可以在map中查找到本地变量的值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null){
aSuppresswarnings ( "unchecked")T result = (T)e.value;
return result;
}
}
//(4)执行到此处, threadLocals为null,调用该更改初始化当前线程的threadLocals变量
return setInitialValue();
}
private T setInitialValue() {
// protected T initialvalue() {return null;}
T value = initialValue() ;
//获取当前线程
Thread t = Thread.currentThread() ;
//以当前线程作为key值,去查找对应的线程变量,找到对应的map
ThreadLocalMap map = getMap(t);
//如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
if (map != null)
map.set(this,value);
//如果map为null,说明首次添加,需要首先创建出对应的map
else
createMap(t, value);
return value;
}
在get方法的实现中,首先获取当前调用者线程,如果当前线程的threadLocals不为null,就直接返回当前线程绑定的本地变量值,否则执行setInitialValue方法初始化threadLocals变量。在setInitialValue方法中,类似于set方法的实现,都是判断当前线程的threadLocals变量是否为null,是则添加本地变量(这个时候由于是初始化,所以添加的值为null),否则创建threadLocals变量,同样添加的值为null
remove实现
public void remove{
ThreadLocalMap m= getMap(Thread.currentThread();
if (mnull) {
m.remove( key: this);
}
}
remove方法判断该当前线程对应的threadLocals变量是否为null,不为null就直接删除当前线程中指定的threadLocals变量
溢出分析
每个线程内部有一个名为threadLocals的成员变量,该变量的类型为 ThreadLocal.ThreadLocalMap类型(类似于一个HashMap),其中的key为当前定义的ThreadLocal变量的this引用,value为我们使用set方法设置的值。每个线程的本地变量存放在自己的本地内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量就会一直存在(所以可能会导致内存溢出),因此使用完毕需要将其remove掉。
看下图:
番外篇
1、ThreadLocalMap 内部 Entry 中 key 使用的是对 ThreadLocal 对象的弱引用,这是为了避免内存泄露,因为如果是强引用,那么即使其他地方没有对 ThreadLocal 对象的引用,ThreadLocalMap 中的 ThreadLocal 对象还是不会被回收,而如果是弱引用则这时候 ThreadLocal引用是会被回收掉的。
2、但是对于的 value 还是不能被回收,这时候 ThreadLocalMap 里面就会存在 key 为 null 但是 value 不为 null 的 entry 项,虽然 ThreadLocalMap 提供了 set,get,remove 方法在一些时机下会对这些 Entry 项进行清理,但是这是不及时的,也不是每次都会执行的,所以一些情况下还是会发生内存泄露,所以在使用完毕后即使调用 remove 方法才是解决内存泄露的最好办法。
3.线程池里面设置了 ThreadLocal 变量一定要记得及时清理,因为线程池里面的核心线程是一直存在的,如果不清理,那么线程池的核心线程的 threadLocals 变量一直会持有 ThreadLocal 变量。
总结
希望这篇文章能够帮助小伙伴们理解ThreadLocal,同时不要忘了三连支持!谢谢!!!
看完三件事❤️
如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
- 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
- 关注公众号 『 阿风的架构笔记 』,不定期分享原创知识。
- 同时可以期待后续文章ing🚀
- 关注后回复【666】扫码即可获取架构进阶学习资料包