ThreadLocal
get
1public T get() {
2 Thread t = Thread.currentThread();
3 ThreadLocalMap map = getMap(t);
4 if (map != null) {
5 ThreadLocalMap.Entry e = map.getEntry(this);
6 if (e != null) {
7 @SuppressWarnings("unchecked")
8 T result = (T)e.value;
9 return result;
10 }
11 }
12 return setInitialValue();
13}
getMap实现:
1ThreadLocalMap getMap(Thread t) {
2 return t.threadLocals;
3}
可以看出,其实ThreadLocalMap作为线程Thread的属性而存在:
1ThreadLocal.ThreadLocalMap threadLocals = null;
Map创建
先来看一下线程的ThreadLocalMap属性不存在的情况,setInitialValue方法:
1private T setInitialValue() {
2 T value = initialValue();
3 Thread t = Thread.currentThread();
4 ThreadLocalMap map = getMap(t);
5 if (map != null)
6 map.set(this, value);
7 else
8 createMap(t, value);
9 return value;
10}
initialValue方法便是我们第一次访问用以获得初始化值的方法:
1protected T initialValue() {
2 return null;
3}
所以,这便解释了我们在使用ThreadLocal时为什么要创建一个ThreadLocal的子类并覆盖此方法。
1void createMap(Thread t, T firstValue) {
2 t.threadLocals = new ThreadLocalMap(this, firstValue);
3}
构造参数为初始增加的一个键值对,从这里可以看出,ThreadLocalMap以ThreadLocal对象为键。
ThreadLocalMap
其声明如下:
1static class ThreadLocalMap {}
那么问题来了,这里为什么要重新实现一个Map,而不用已有的HashMap等类呢?基于以下几点考虑:
- 所有方法均为private。
- 内部类Entry继承自WeakReference,当内存紧张时可以对ThreadLocal变量进行回收,注意这里并没有结合ReferenceQueue使用。
构造器源码:
1ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
2 table = new Entry[INITIAL_CAPACITY];
3 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
4 table[i] = new Entry(firstKey, firstValue);
5 size = 1;
6 setThreshold(INITIAL_CAPACITY);
7}
setThreshold:
1private void setThreshold(int len) {
2 threshold = len * 2 / 3;
3}
和HashMap的套路一样,只不过这里负载因子写死了,2 / 3,强调一下,不是3 / 4 !!!
set
1public void set(T value) {
2 Thread t = Thread.currentThread();
3 ThreadLocalMap map = getMap(t);
4 if (map != null)
5 map.set(this, value);
6 else
7 createMap(t, value);
8}
正如注释中所说,我们在使用ThreadLocal时应该去覆盖initialValue方法,而不是set。显然这里的核心便是ThreadLocalMap的set方法:
1private void set(ThreadLocal<?> key, Object value) {
2 Entry[] tab = table;
3 int len = tab.length;
4 int i = key.threadLocalHashCode & (len-1);
5 for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
6 ThreadLocal<?> k = e.get();
7 //bin里的第一个节点即为所需key,更新value
8 if (k == key) {
9 e.value = value;
10 return;
11 }
12 if (k == null) {
13 replaceStaleEntry(key, value, i);
14 return;
15 }
16 }
17 tab[i] = new Entry(key, value);
18 int sz = ++size;
19 if (!cleanSomeSlots(i, sz) && sz >= threshold)
20 rehash();
21}
注意
ThreadLocalMap的底层实现貌似是基于一个叫做"Knuth Algorithm"的算法,在这里不再细究其实现细节,但有几个地方值得注意。
扩容
不同于Map接口的实现,ThreadLocalMap的扩容似乎没有上限限制,resize方法部分源码可以证明:
1private void resize() {
2 Entry[] oldTab = table;
3 int oldLen = oldTab.length;
4 int newLen = oldLen * 2;
5 Entry[] newTab = new Entry[newLen];
6 //...
7}
哈希冲突
不同于喜闻乐见的HashMap用链表 + 红黑树的方式解决哈希冲突,这里用的应该是线性探查法,即如果根据哈希值计算得来的位置不为空,那么将继续尝试下一个位置。
这一点可以从resize方法的下列源码得到证明:
1int h = k.threadLocalHashCode & (newLen - 1);
2while (newTab[h] != null)
3 h = nextIndex(h, newLen);
4newTab[h] = e;
哈希值
ThreadLocalMap使用的哈希值源自ThreadLocal的下列属性:
1private final int threadLocalHashCode = nextHashCode();
2private static int nextHashCode() {
3 return nextHashCode.getAndAdd(HASH_INCREMENT);
4}
而nextHashCode属性则是AtomicInteger类型,HASH_INCREMENT定义:
1private static final int HASH_INCREMENT = 0x61c88647;
清除
由于ThreadLocalMap的key(即ThreadLocal)为弱引用,所以当其被回收时,势必需要将value置为null以便于进行垃圾回收。那么这个清除的时机又是什么呢?
答案是get, set, remove都有可能。
Lambda支持
jdk8支持使用以下方式进行初始化:
1ThreadLocal<String> local = ThreadLocal.withInitial(() -> "hello");
withInitial源码:
1public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
2 return new SuppliedThreadLocal<>(supplier);
3}
SuppliedThreadLocal是ThreadLocal的内部类,也是其子类:
1static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
2 private final Supplier<? extends T> supplier;
3 SuppliedThreadLocal(Supplier<? extends T> supplier) {
4 this.supplier = Objects.requireNonNull(supplier);
5 }
6 @Override
7 protected T initialValue() {
8 return supplier.get();
9 }
10}
一目了然。
内存泄漏
以下两篇博客足矣:
继承性问题
子线程中是否可以获得父线程设置的ThreadLocal变量? 答案是不可以,如以下测试代码:
1public class Test {
2
3 private ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
4
5 public void test() throws InterruptedException {
6 threadLocal.set("parent");
7
8 Thread thread = new Thread(() -> {
9 System.out.println(threadLocal.get());
10 threadLocal.set("child");
11 System.out.println(threadLocal.get());
12 });
13
14 thread.start();
15
16 thread.join();
17
18 System.out.println(threadLocal.get());
19 }
20
21 public static void main(String[] args) throws InterruptedException {
22 new Test().test();
23 }
24
25}
执行结果是:
1null
2child
3parent
从中可以得出两个结论:
- 子线程无法获得父线程设置的ThreadLocal。
- 父子线程的ThreadLocal是相互独立的。
解决方法是使用java.lang.InheritableThreadLocal类。原因其实很容易理解: ThreadLocal数据以线程为单位进行保存,InheritableThreadLocal的原理是在子线程创建的时候 将父线程的变量(浅)拷贝到自身中。
从源码的角度进行原理的说明,Thread中其实有两个ThreadLocalMap:
1ThreadLocal.ThreadLocalMap threadLocals = null;
2ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
普通的ThreadLocal被保存在threadLocals中,InheritableThreadLocal被保存在inheritableThreadLocal中,注意这里是并列的关系,即两者可以同时存在且不为空。另外一个关键的问题便是 父线程的变量是何时被复制到子线程中的,答案是在子线程创建时,init方法:
1private void init(ThreadGroup g, Runnable target, String name,
2 long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
3 if (inheritThreadLocals && parent.inheritableThreadLocals != null)
4 this.inheritableThreadLocals =
5 ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
6}
inheritThreadLocals除非我们使用了带AccessControlContext参数的构造器,默认都是true。
然而到了这里仍有问题存在:那就是线程池场景。一个线程只会在创建时从其父线程中拷贝一次属性,而线程池中的线程需要动态地执行从不同的上级线程提交地任务,在此种情形下逻辑上的 父线程也就不再存在了,阿里巴巴的transmittable-thread-local解决了这一问题,核心原理其实是实现了一个Runnable的包装, 伪代码如下:
1public class Wrapper implements Runnable {
2
3 private final Runnable target;
4
5 @Override
6 public final void run() {
7 //1.拷贝父变量
8 try {
9 target.run();
10 } finally {
11 //2.还原...
12 }
13 }
14}