之前把《现代操作系统》的前四章看完了,收获还是很大的,尤其在进程管理那一章让我明白了并发在操作系统上是如何控制的。所以,由此也引发了这一篇源码解析文章的创作。原本是打算写AQS的源码解析的,但是最近项目比较忙,没有时间,只能先写比较简单的ThreadLocal。
PS:最近都在看《计算机网络 谢希仁编著》 ,感觉跟之前看的完全不是一本书,就是怎么变得那么简单了,可能这就是境界上的提升吧,如果LOL的段位也有那么简单提升就好了,哈哈哈哈(可能是我依然没有领悟它的精髓)。
ThreadLocal 的使用是非常简单的
public class TestThreadLocal {
private static ThreadLocal<String> local = new ThreadLocal<>();
private static void print(String t){
System.out.println(t + ":" + local.get());
}
public static void main(String[] args) {
Thread threadOne = new Thread(() -> {
local.set("threadOne local variable");
print("threadOne");
});
Thread threadTwo = new Thread(() -> {
local.set("threadTwo local variable");
print("threadTwo");
});
threadOne.start();
threadTwo.start();
}
}
复制代码
这段代码的输出结果就是
threadOne: threadOne local variable
threadTwo: threadTwo local variable
代码执行流程:
线程 threadOne 通过 local.set("threadOne local variable") 在线程内部设置了一个值,然后通过 local.get() 可以获取到这个在线程内部的值。
线程 threadTwo 同理
为什么在两个线程内部调用同一个 local.get() 方法却可以获取到不同的值?
在解释这个问题之前,我们先来讨论另一个问题,通过 local.set() 方法设置的值到底存放在哪里?
我们先来看一下java的源码,都是大佬写的,要细细的品,给大佬应有的尊重!
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
复制代码
虽说是大佬写的代码,但是从表面上来看,还是很简单的,哈哈哈哈!
我梳理一下这段代码都做了什么:
getMap(t)
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
复制代码
这段代码返回了线程对象中的 threadLocals 变量。
纳尼???线程对象里面竟然还有这个变量?
没错,它确实存在着,如果你只阅读过 Thread 类的源码,确实可能会把这个变量忽略,因为 Thread 类中的绝大部分方法都是跟该变量无关的。
我使用的是jdk8,所以在 Thread 类中的181行就是该变量的声明。
ThreaLocal 类维护的。这里的map就是指
ThreadLocalMap
。
ThreadLocalMap是一个定制的Map,实际的使用上跟我们的HashMap是类似的,只是多了一个内部类Entry,该类是一个弱引用类,这个会在下一篇的get方法讲,这里就把map当作是一个我们平时使用的Map就可以了。
所以我们大概可以回答上面提出的那个问题,值到底存放在哪里?从这里,我们知道值是存放在 Thread 线程对象里面的。
实际上值是存在ThreadLocal的ThreadLocalMap里面,而map又存放在线程对象里面,所以间接的存放在线程对象里面
为什么要用Map来存呢?我觉得应该是因为一个线程对象可以有很多个 ThreaLocal 对象。List也可以存多个值,但是使用List来存的话,我们取值时就不知道应该取哪一个值了。
让我们言归正传,继续看set方法
如果map不为空,那么就将ThreadLocal对象作为键,参数value作为值,通过map的 set() 方法进行设置。
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
复制代码
如果map为空,那么就对map进行初始化。
void createMap(Thread t, T firstValue) {
// 创建一个ThreadLocalMap,并且设置第一个键值对,等同于map.set(this, value)
// 然后赋值给当前线程对象的threadLocals变量
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
复制代码
map将ThreadLocal对象作为键,那我们就可以大胆猜测测试代码中 local.get() 方法是怎么获取值的。
ThreadLocal的get()基本步骤是这样的,但是因为传递进来的value实际上是要被弱引用的,所以具体实现会有一些不同。至于弱引用我会在后面单独作为一个专栏来讲。