之前把《现代操作系统》的前四章看完了,收获还是很大的,尤其在进程管理那一章让我明白了并发在操作系统上是如何控制的。所以,由此也引发了这一篇源码解析文章的创作。原本是打算写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实际上是要被弱引用的,所以具体实现会有一些不同。至于弱引用我会在后面单独作为一个专栏来讲。