前言

ThreadLocal是一个全局变量,用于解决线程范围内变量共享问题,其实ThreadLocal可以看作是一个Map集合,而key就是当前线程名,而value是想要存放的变量,ThreadLocal的变量用完会自动销毁,不用考虑ThreadLocal中的变量会占用空间,对于ThreadLocal来说每个线程的变量都是独立的,不会相互影响,可以通过ThreadLocal在同一线程内的不同组件传递公共变量

成员变量

我们看ThreadLocal的源码可以看到他有三个成员变量

/**
这个属性用于ThreadLocal的桶位寻址
如果一个threadLocal对象第一次调用get()方法,那么就会给当前线程分配一个value
这个value和这个threadLocal对象被包装成一个entry
这个entry的key是threadLocal对象,value是生成的value
**/
private final int threadLocalHashCode = nextHashCode();
/**
这个属性是创建ThreadLocal会用到该属性
每创建一个ThreadLocal对象,就会使用该属性分配一个hash值给这个对象
**/
private static AtomicInteger nextHashCode = new AtomicInteger();
/**
表示hash增量,没创建一个ThreadLocal对象,ThreadLocal.nextHashCode就会增长HASH_INCREMENT
**/
private static final int HASH_INCREMENT = 0x61c88647;

get方法

public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//根据当前线程获取ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//判断map是否为null
if (map != null) {
//当前threadLocal对象作为key获取entry
ThreadLocalMap.Entry e = map.getEntry(this);
//判断entry是否为null
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
//返回result
return result;
}
}
//初始化
return setInitialValue();
}

Java并发之ThreadLocal_初始化

接下来我们再看这个初始化方法

setInitialValue方法

这个方法是用于初始化的,我们从上面的get方法可以知道,进入该方法有两种情况,一个就是ThreadLocalMap为null或者Entry为null

private T setInitialValue() {
//调用ThreadLocal对象的方法进行初始化
T value = initialValue();
//获取到当前线程
Thread t = Thread.currentThread();
//当前线程作为key获取ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//判断map是否为null,如果不为null证明已经初始化过ThreadLocalMap对象了(一个ThreadLocal只会初始化一次)
if (map != null)
//向ThreadLocalMap中保存当前threadLocal与当前线程生成的线程局部变量
map.set(this, value);
else
//如果走到这里证明map==null,进行初始化创建一个ThreadLocalMap对象
createMap(t, value);
return value;
}

Java并发之ThreadLocal_初始化_02

set方法

public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//将当前线程作为key获取ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//判断threadLocalMap是否为null
if (map != null)
//覆盖或者添加新的key - value
map.set(this, value);
else
//证明还没有被初始化,进行初始化
createMap(t, value);
}

Java并发之ThreadLocal_get方法_03

如何实现线程隔离?

每一个Thread都有着自己的ThreadLocalMap用于存储数据,当线程访问某个ThreadLocal对象的get方法的时候,方法内部会判断该线程的ThreadLocalMap数组内是否存在以当前线程为key的Entry节点,如果数组内没有对应的节点,那么当前ThreadLocal对象,就会调用初始化方法,创建一个Entry节点放入ThreadLocalMap中

ThreadLocal内存泄漏原因

主要是因为ThreadLocalMap的Entry对象是弱引用,对于弱引用,JVM在每次GC的时候都会清理他们,此时key就会变为null,如果此时没有其他强引用指向value,那么此时value就不会被访问到,按道理来说value应该也会被GC,但是此时ThreadLocalMap的Entry对象对value是强引用,这样就会导致JVM始终无法GC value,这样就导致了value成了一个始终无法清理的value