首先来看看ThreadLocal的创建
private static final ThreadLocal<String> USER_INFO = new ThreadLocal<>();
看看ThreadLocal对象的创建过程, 可能有些人说很简单就是new了个对象,但我觉得其实其中有些很重要的细节,我们看看他的成员变量和静态变量, 下面我只贴出了我们需要注意的部分源码
public class ThreadLocal<T> {
/**
* ThreadLocals rely on per-thread linear-probe hash maps attached
* to each thread (Thread.threadLocals and
* inheritableThreadLocals). The ThreadLocal objects act as keys,
* searched via threadLocalHashCode. This is a custom hash code
* (useful only within ThreadLocalMaps) that eliminates collisions
* in the common case where consecutively constructed ThreadLocals
* are used by the same threads, while remaining well-behaved in
* less common cases.
*/
private final int threadLocalHashCode = nextHashCode();
/**
* The next hash code to be given out. Updated atomically. Starts at
* zero.
*/
private static AtomicInteger nextHashCode =
new AtomicInteger();
/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647;
/**
* Returns the next hash code.
*/
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
....省略。。。
}
private final int threadLocalHashCode = nextHashCode();这个成员的作用是什么呢, 其实你就把他理解为每个ThreadLocal实例对象的身份证,注意:每new一个新的对象, 它的值是唯一的, 为什么需要这个, 我下面会解释。private static final int HASH_INCREMENT = 0x61c88647;可以看出它是私有的且唯一的, 外界不可修改。其实它是threadLocalHashCode的初始值。private static AtomicInteger nextHashCode = new AtomicInteger();线程安全的计数器, 其实就是用来生成threadLocalHashCode 以HASH_INCREMENT为初始值, 每次实例化一个ThreadLocal对象就加一。
总结 每次new ThreadLocal() 会为它本身赋值一个唯一身份标识threadLocalHashCode, 这个变量的初始值为HASH_INCREMENT, 每次实例化的时候, 就会调用nextHashCode() 加一。
使用ThreadLocal set方法存储值
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
看set源码, value参数就是你要存储的值, 首先会获取当前线程对象, 第二步获取当前线程的ThreadLocalMap 大家可以去看看getMap(t) 的源码,其实ThreadLocalMap 就是Thread类的成员变量。初始值为null。那么当你的线程第一次使用set方法的时候, 这个变量毫无疑问是null, 所以它会去调用 createMap(t, value); 接下来让我们看看它的源码
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
好家伙, 其实只是new ThreadLocalMap(this, firstValue) 然后赋值给当前线程对象对吧。 所以还要继续看看new ThreadLocalMap(this, firstValue) 干了啥, 为啥就能在ThreadLocal中保存线程所需要存储的变量呢?
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
我们看看ThreadLocalMap的构造器, 传入了当前ThreadLocal对象, 和你需要存储的value值。 第一步先初始化了一个Entry数组, 这个entry你就理解为一个键值对吧,key就是ThreadLocal对象, value就是你真实要存储的值。第二步, 其实就是拿了ThreadLocal对象的唯一身份Id(就是threadLocalHashCode), 然后计算出此ThreadLocal对象应该存储在Entry数组的索引值, 最后就是创建Entry对象, 放入Entry数组中。Entry对象存储了真实的value值。
总结: 在线程第一次使用ThreadLocal对象存储value值时, 干了这么几件事情。1.初始化线程的ThreadLocalMap对象 2. ThreadLocalMap对象创建对应的entry保存value值, 并根据ThraadLocal的唯一身份标识计算应该存入到entry数组的对应位置。这就是我们调用# ThreadLocal set方法所发生的事情。
使用 ThreadLocal get方法获取值
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
- 首先还是获取当前的线程对象
- 获取当前线程对象的 ThreadLocalMap 它就是threadLocal的一个属性而已, 上文已经描述过。
- 根据ThreadLocalMap 获取对应的entry 上文讲过, ThreadLocalMap会保存一个entry数组, 那么它会根据ThreadLocal的唯一身份标识计算出索引的位置,然后获取出来。
- 从entry对象中, 获取你真实存储的value值。
总结: 这就是我为什么说ThreadLocal的创建过程有些细节要了解。因为在整个过程中都是使用threadLocalHashCode 来决定entry应该放在数组的哪个位置。本篇是围绕线程第一次创建ThreadLocalMap来讲的, 其实若不是第一次创建(也就是你不止一个ThreadLocal对象), 那么只是往ThreadLocalMap的entry数组中, 添加你其他的ThreadLocal对象要存储的value值。
最后附上一张它们的关系图:
其实就是 一个Thread对象有一个对应的ThreadLocalMap对象的成员变量, 然后ThreadLocalMap对象里面保存了entry数组, 那么entry数组是根据ThreadLocal的唯一身份id计算出索引位置,保存对应的entry信息, entry信息里面就保存了你想要的value。那么这也就是你能创建N个不同类型的ThreadLocal,保存不同类型的value值挂到线程上的原因。