ThreadLocal深入分析
ThreadLocal是什么
从名字我们就可以看到ThreadLocal叫做线程变量,
意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的
ThreadLocal为变量在每个线程中都创建了一个副本,
那么每个线程可以访问自己内部的副本变量。
从字面意思来看非常容易理解,但是从实际使用的角度来看,就没那么容易了,作为一个面试常问的点,使用场景那也是相当的丰富:
1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、Spring关于事务的处理,用于存储线程事务信息。
4、Mybatis关于分页的处理
ThreadLocal的initialValue()方法的流程图
代码理解下
关于initialValue()方法,是由子类重写的,当get时。没有set的时候就去获取init方法。否则获取set的值
public class Profiler {
// 第一次get()方法调用时会进行初始化(如果set方法没有调用)
//每个线程会调用一次
private static final ThreadLocal<Long> TIME_THREADLOCAL =
new ThreadLocal<Long>() {
//初始化重写了返回1,第一次get()方法调用时,没有set就会返回
protected Long initialValue() {
return 1L;
}
};
public static final void begin() {
//TIME_THREADLOCAL.set(10L);
}
public static final long end() {
return TIME_THREADLOCAL.get();
}
public static void main(String[] args) throws Exception {
Profiler.begin();
TimeUnit.SECONDS.sleep(1);
System.out.println("get取到的值:" + Profiler.end() + " ");
}
}
当begin方法里没有set时,get方法取到的是初始化的值。
当begin方法里有set方法时,get方法取到的是set的值
ThreadLocal深入分析
我们看下这个例子
public class ThreadLocal_learn {
public static void main(String[] args) {
ThreadLocal<Person> threadLocal=new ThreadLocal<>();
Person person=new Person("张三");
new Thread(()->{
threadLocal.set(person);
System.out.println("线程"+Thread.currentThread().getName()+":"+threadLocal.get().name);
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程2
new Thread(()->{
System.out.println("线程"+Thread.currentThread().getName()+":"+threadLocal.get());
}).start();
}
}
class Person{
String name;
public Person(String name) {
this.name = name;
}
}
定义个全局的对象person,在第一个线程里set,在第二个线程里get,那么我们能拿到吗,看运行结果
可以看见线程2是拿不到线程1里set的变量值的。分析源码之前呢,先来讲个基础的
JAVA中引用类型有哪些?每种引用类型的特点?
- 强引用,软引用,弱引用,虚引用
- 强引用不会被回收,即使OOM也不会被回收
软引用当空间不足时会回收。适合当缓存使用
弱引用看见直接回收。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);
}
获取当前线程,调用getMap方法
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
t是当前线程,threadLocals 是当前前程的成员变量ThreadLocalMap
也就是每个线程都有这个map,键就是ThreadLocal,值就是传进来的值。
然后看map.set(this, value);
会发现是new了一个Entry,这就是键值对。
进入这个
会发现这个Entry继承了个弱引用,调用了弱引用的构造方法,key就是ThreadLocal对象,值就是传进去的值。
因为ThreadLocalMap是属于线程本身的所以,不同的线程是只能拿本线程的变量。所以在上面的例子里线程2是拿不到线程1的对象
ThreadLocal的内存泄漏
那么我们看下为什么
这里用到了弱引用?
还是看这个图
但是即使这样依然还会有内存泄漏的存在。
ThreadLocal对象被回收,key的值就是null,则会导致整个value再也无法被访问到,因此依然存在内存泄漏。所以需要把这个Entry给remove掉
并且在线程池的时候,用完一定要手动调用remove。因为如果不清理,用的就是原来旧的值。