`紧接上篇:浅谈ThreadLocal(一)
本篇我们结合源码的方式去回答上篇文末的两个问题
- Q1:ThreadLocal和Thread之间的关系到底如何?
- Q2: 为什么每启动一个线程就会调用一次
initialValue()呢?
一、案例回顾
1.1 自定义MyThreadLocal
在回答Q1前,我们先根据上篇中我们了解的ThreadLocal来模仿写一个MyThreadLocal。
public class MyThreadLocal<T>{
private T t;
protected T initialValue(){return null;}
public T get() {
//如果没值,则调用initialValue初始化一个值
if (t == null){
return initialValue();
}
return t;
}
}
然后修改一下ThreadId类,改用我们刚写的MyThreadLocal去管理线程Id。
public class ThreadId {
private static final AtomicInteger nextId = new AtomicInteger(0);
private static final MyThreadLocal<Integer> threadId = new MyThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
public static Integer get() {
return threadId.get();
}
public static void main(String[] args) {
new Thread(() -> System.out.println(ThreadId.get())).start();
new Thread(() -> System.out.println(ThreadId.get())).start();
new Thread(() -> System.out.println(ThreadId.get())).start();
new Thread(() -> System.out.println(ThreadId.get())).start();
}
}
看运行结果,发现跟用ThreadLocal一毛一样。
什么情况,我用短短几行代码就实现了ThreadLocal的功能了?当然不是,我们如下修改一下测试代码,MyThreadLocal马上现原形。
1.2 原因分析
测试代码就不贴了,直接对比看结果。
1.3 分析源码
这时不禁更加好奇了,ThreadLocal怎么做到管理不同线程id的呢?我们结合get()源码来探寻一下
这里我们看到冒出来两个新东西 ThreadLocalMap 和 ThreadLocalMap.Entry。这两具体内容是什么先不管,但是类比Map和Map.Entry的关系。这两个应该也大差不差。继续看get()
给大家分享个小技巧:先看代码注释,再去追源码会事半功倍。上图中作者告诉我们:这个方法返回当前线程的thread-local变量的值。如果这个变量没有值,那么就会调用initialValue()返回一个初始值。也就是说每个线程都有一个叫thread-local的变量咯?我们再证实一下Thread中是否有这个变量。发现它确实存在,但它不是ThreadLocal,而是ThreadLocalMap。
那赶紧看看ThreadLocalMap和ThreadLocal又是怎么个事吧。先看关系图,ThreadLocal中有个内部类ThreadLocalMap,ThreadLocalMap中又套了个Entry类。
再看源码:
好家伙,Entry继承了WeakReference接口,泛型又是ThreadLocal。自此我们梳理一下Thread和ThreadLocal的关系。
1.4 总结
1.4.1 ThreadLocal和Thread之间的关系
从我们分析的结果来看,ThreadLocal和Thread之间并没有直接关系。非要说,有的话也可以。因为ThreadLocal是ThreadLocalMap的key。
1.4.2 为什么每启动一个线程就会调用一次initialValue()呢?
其实我们总结出上图的关系后,在看这个问题时会恍然大悟。因为每个线程都有自己的ThreadLocalMap去存储value。每个线程在调用ThreadId.get()的时候,都会因为自己的ThreadLocalMap中没有value,而调用initialValue().因此尽管不同的线程共用一个ThreadLocal作为key,但是由于ThreadLocalMap不同,它们之间互不干扰。
最后我们知道了原来ThreadLocal不存值,我们通过它提供的get()实际上是取的Entry的value,它自己也是作为key存在Map中的。当时在使用上我们不用关心ThreadLocal的内部实现细节,只要通过它提供的set()/get()就能操作线程本地变量。是不是很像工具类的感觉。封装内部的具体实现,提供接口调用。
下篇我们接着探讨:为什么说ThreadLocal可以解决线程安全问题?Threadlocal为什么要用弱引用?ThreadLocal有哪些使用场景,要注意哪些问题? 传送门:ThreadLocal终章