前言:
ThreadLocal<T>为每个线程提供一个独立的空间,用于存储每个线程自己的变量,多线程并发访问不会出现竞争等并发问题。内部用到了自定义的ThreadLocalMap,但是key值是线程对应的ThreadLocal对象而不是线程本身。ThreadLocal的本质是让每个线程都持有一个key为该ThreadLocal对象的Map。这样每次在get或者set的时候直接取线程的该Map通过该ThreadLocal对象(key)对其进行get或者set操作即可。再往深处挖就会发现Map本质上是一个Entry数组,而Entry持有的key实际上是ThreadLocal对象的弱引用,在ThreadLocal对象的引用变为null时ThreadLocal对象可能被垃圾回收掉,而此时弱引用也会变为null。我们常用的private static声明的ThreadLocal一般不会被垃圾回收,因此Map可以一直持有该对象。
1、ThreadLocal重要成员
//ThreadLocal本身作为Map的key的hash值,用于存放数据的时候对key的均匀分布
private final int threadLocalHashCode = nextHashCode();
//下一个hash值
private static AtomicInteger nextHashCode =
new AtomicInteger();
//计算下一个hash值的增量
private static final int HASH_INCREMENT = 0x61c88647;
//这是ThreadLocal能存储数据的基础
static class ThreadLocalMap
ThreadLocal的成员变量不多,其中最重要的就是静态内部类ThreadLocalMap了,剩下计算hash值的两个变量我们后续用到的时候再说,这里先留个钩子。
2、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);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
get/set方法是我们最常用的方法,方法实现也很简单,取当前线程-->取当前线程关联的Map-->调用Map的put/get/remove方法。着重分析里面的getMap和createMap方法。
//获取Map拿到的是当前线程相关联的ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocal.ThreadLocalMap threadLocals = null;
//创建的Map是内部类ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
这里创建的Map是ThreadLocalMap,其中的Key是ThreadLocal对象本身而不是线程,value是你调用set方法设置的value。
分析到这可以发现问题的关键在于ThreadLocalMap这个内部类:
3、ThreadLocalMap:
3.1、成员变量
//用来存储数据的最小单元,本质上是一个弱引用,在垃圾回收的时候如果对ThreadLocal的引用都是弱引用那么回收该对象
static class Entry extends WeakReference<ThreadLocal> {
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
//初始容量,必须是2的次方数
private static final int INITIAL_CAPACITY = 16;
//Entry桶,长度为2的倍数
private Entry[] table;
//桶中Entry的个数
priva