1.是什么
ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程上下文。
我们可以得知 ThreadLocal 的作用是:提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度。
- 线程并发: 在多线程并发的场景下
- 传递数据: 我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量
- 线程隔离: 每个线程的变量都是独立的,不会互相影响
2.内部结构
1.每个Thread线程内部都有一个Map(ThreadLocalMap)
2.Map里面存储ThreadLocal对象(key)和线程的变量副本(value)
3.Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向Map获取和设置线程的变量值
4.对于不同的线程,每次获取副本值时,别的线程都不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。
3.源码分析
set方法
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//如果map不为null
if (map != null)
//将threadLocal对象作为key,传入的参数作为value封装为entry放入ThreadLocalMap中
map.set(this, value);
else
//见下
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
//直接返回当前线程维护的ThreadLocalMap对象
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
//将调用set方法的threadLocal对象作为key,传入的值作为value,设置为第一个entry并放入ThreadLocalMap中
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
总结:1.获取当前线程,并根据当前线程对象获取内部的ThreadLocalMap
2.如果ThreadLocalMap不为null,则将参数设置到Map中(当前threadLocal作为key)
3.如果ThreadLocalMap为null,则给该线程创建一个ThreadLocalMap,并设置初始值
get方法
public T get() {
//获取当前线程对象
Thread t = Thread.currentThread();
//获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
//如果不为null,则将this(threadLocal)作为key,获取值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
//如果获取到的entry不为null,返回此值
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//两种情况:1.当map为null时候 2.当map不为null,但是当前threadLocal没有关联的entry
return setInitialValue();
}
private T setInitialValue() {
//返回null,如果相反会其他值可以继承ThreadLocal重写
T value = initialValue();
//当前线程
Thread t = Thread.currentThread();
//获取map
ThreadLocalMap map = getMap(t);
if (map != null)
//将当前ThreadLocal作为key,value作为null放入map中
map.set(this, value);
else
//此时构造器新建一个map,threadLocal对象作为key,传入的值作为value,设置为第一个entry并放入ThreadLocalMap中
createMap(t, value);
//返回默认值 null
return value;
}
总结:1.首先尝试根据当前线程获取到ThreadLocalMap
2.如果获取的ThreadLocalMap不为null,以ThreadLocal作为key来查找对应的entry,如果找不到,则转入第四步
3.如果获取到的entry不为null,则返回对应的value,结束
4.如果ThreadLocalMap或者entry为null,则通过initialValue()赋予默认值(null),然后将当前ThreadLocal作为key,null作为value放入ThreadLocalMap并返回
整个过程简而言之就是 先获取线程的ThreadLocalMap,如果存在则返回value,不存在则创建并返回初始值(null)
remove方法
public void remove() {
//通过当前线程获取其ThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
//不为null,直接移除
if (m != null)
m.remove(this);
}
4.ThreadLocalMap的结构
static class ThreadLocalMap {
//ThreadLocalMap中的实体数据,继承了弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
//可以看到Entry的key是一个弱引用,而且必须是ThreadLocal对象
//其目的就是将ThreadLocal的生命周期与线程的生命周期解绑
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//默认容量
private static final int INITIAL_CAPACITY = 16;
//位桶数组
private Entry[] table;
//entry的个数
private int size = 0;
//扩容阈值
private int threshold;
}
在使用ThreadLocal过程中可能会出现内存泄漏的情况,但是内存泄漏和Entry中使用的弱引用key是没有关系的。
上图为假设Entry的key为强引用
如果线程生命周期还未结束,ThreadLocal使用完毕,我们需要将其回收,所以我们将ThreadLocalRef置为null,但是此时我们的key是强引用,在堆中的ThreadLocal是无法被回收的,所以造成了内存泄漏
正确的姿势
如果线程生命周期还未结束,ThreadLocal使用完毕,我们需要将其回收,所以我们将ThreadLocalRef置为null,此时我们的key是弱引用,在下次垃圾回收器执行时我们的ThreadLocal就会被回收掉,此时entry就置为null了。但是现在出现了一个问题,线程执行完毕之前我们的value是无法被回收的。
综上:ThreadLocal的内存泄漏根源是,ThreadLocalMap的生命周期和线程的生命周期一样长,如果没有手动删除对应的entry就会导致内存泄漏
避免内存泄露的两种处理方式:
1.使用完ThreadLocal,调用remove方法删除对应entry
2.使用完ThreadLocal,线程也随之结束
第二个方法有个问题,如果我们使用线程池,核心线程是无法被销毁的。也就导致内存泄漏一直存在
既然使用弱引用与强引用都无法避免内存泄漏,那我们为什么还要使用弱引用作为key呢?
其实在ThreadLocalMap中的getEntry和setEntry中,会进行判断,如果key为null,是会将对应的value也会置为null的。这就意味着key是弱引用的情况下,如果ThreadLocalRef置为null,在垃圾回收器执行后,key也会被置为null,此时value也会被置为null,从而避免内存泄漏