浅谈ThreadLocal(一)
前言
ThreadLocal是什么?它可以用来解决什么问题?它是怎么解决这些问题的?如果你也有上述疑问的话,希望本系列文章可以帮你带来一些帮助。
ps: ThreadLocal源码虽然不多但是讲清楚却不容易,因此我打算写为ThreadLocal写系列文章。而且我也不会一上来就直接分析ThreadLocal的set()
、get()
、ThreadLocalMap
等源码 ,那样不过是粗暴的扒了下源码却体会不到ThreadLocal
的设计思想。我更愿意从ThreadLocal
作者的角度去分析它,以作者的角度去解答What is ThreadLocal?
正文
1. ThreadLocal是什么
首先ThreadLocal这个变量名容易让人费解,“线程本地”是个啥意思,是“本地线程“吗?网上翻译成中文大部分叫它“线程本地变量”/“线程变量”。但个人理解ThreadLocal
是用来管理线程变量
的工具类。
1.1 作者眼中的ThreadLocal
每个技术的出现肯定都是为了解决现实问题的,咱们看看作者是怎么介绍的。
大致含义就是ThreadLocal这个类提供线程本地变量(注意图中variables用的是复数,即多个变量),这些变量彼此之间不一样。怎么不一样呢,每个线程都可以通过set()
或get()
操作属于自己的变量。而且ThreadLocal实例一般会设置成静态私有属性,以达到状态和线程关联的目的。
大白话:ThreadLocal用于管理变量副本,这些变量副本在线程之间是独立隔离的。ThreadLocal实例一般会设置成静态私有属性
接着,作者给了我们例子说明,如图:
看代码注释,这个例子是利用ThreadLocal管理了每个线程的id。
public class ThreadId{
// Atomic integer 存储下一个线程ID
private static final AtomicInteger nextId = new AtomicInteger(0);
// threadId存储每个线程的id,可以看到这里ThreadLocal设置成了静态私有属性,一般都会这么用
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
// 返回当前线程id
public static int 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();
}
}
我们把该案例测试运行一下,结果如下图:
结果: main()
中我们创建了4个线程,每个线程都调用了ThreadId.get()获取了id,然后得到了每个线程的ID。
Q1: ThreadId类中的静态变量threadId,它是随着类加载而加载的。也就是说threadId在内存中只有一份,但是为何它能存多个值呢?
Q2: 即使它能存多个值,但是按理说initialValue()
是在threadId加载到内存中时才被调用,而类只加载一次,threadId也就只会实例化一次,那么initialValue()
也就只调用一次。那为什么程序运行的结果是“0,1,2,3”而不是“0,0,0,0”呢?
A1: 也有Q1问题的小伙伴说明你还记得static修饰符对变量的生命周期的影响。确实,如果把threadId
的类型换成任意一个基本数据类型,那么它是不可能可以存多个值的。但是它是TheadLocal
呀,它的内部其实是用了一个Map去存数据的。千万别误会这个map是不是用了多个key存不同线程的id。因为这个map只有一个key就是threadLocal
。而在本例中threadLocal
是静态的,那么它在内存中只有一份,也就是说4个线程的key相同的。真正的奥义在于这个map是不同的。这里先给结论:ThreadLocal是利用ThreadLocalMap存值的,而它能存多个线程id的原因是每个线程都有自己的ThreadLocalMap,这样即使key相同,但是这个key存在不同map中从而实现存多个线程id的目的。 具体实现我们后面看源码。
A2: 我们先看看作者对initialValue()
的说明
第一段大致是说:这个方法返回当前线程“thread-local”变量的“初始值”。 这个方法被调用的情况:
- 第一次调用
set()
前调用了get()
去取值。也就是没有设置值时就去取值了那么会先调用initialValue()
初始化一个值。
一般情况下,这个方法每个线程最多被调用一次,除非调用remove()
后马上又调用了get()
。
第二段大致是说:这个方法一般返回null
,如果程序员想要返回其他值那么必须继承ThreadLocal类去重写initialValue()
。一般我们会用匿名内部类的方式重写initialValue()
。
结论: 看到这大致懂了,首先上述案例里我们没有调用set()
直接get()
去取值,因此initialValue()
会执行。其次,我们用匿名内部类的方式重写了initialValue()
,让它每次返回不同的线程id。等等,好像还是没解释initialValue()
为什么会执行多次,按作者的话说每个线程最多执行一次。眼尖的小伙伴可能已经发现了,每个线程最多执行一次,我们有4个线程,因此执行4次也是无可厚非的。那么为什么每启动一个线程就会调用一次呢?ThreadLocal和Thread之间的关系到底如何?
下篇:传送门
转载自:https://juejin.cn/post/7220981386407084069