ThreadLocal、InheritableThreadLocal 使用场景

一开始啊,我一直没想通,到底 ThreadLocal 这玩意怎么使用。为什么可以防止多线程,我是知道的,我看了它的源码。我也知道是一种泛型的对象,然后每个线程都保留一份。
下面不对源码做过多的介绍,源码详情可参考之前分析的 ThreadLocal:18. 并发容器之ThreadLocal
最近两天,我碰到了一个问题,然后我想到了它可以解决,我思考到了 ThreadLocal 的用途,同时也想通了 InheritableThreadLocal 的用途。

举例说明 ThreadLocal 用途

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ThreadLocal<T> 的作用:每个线程都持有一个 T 类型的变量(准确来说是每个线程都有一个 T 类型的对象,这个是线程间私有的。)

举个例子:下面是一个方法的调用链路,从 a 方法,执行到 z 方法。(假设为整个流程为单线程执行)
a -> b -> c -> ... -> z

假设,此时 a 方法里面有一个 Date 类型的变量 'Date startTime = ...;',
但是呢,我没有往下传,也就是说 b方法,c方法,...,z方法 中都没有 'Date startTime' 这个变量。

那么问题来了,a 中定义整个对象,但是只有 z方法 会用到这个变量。(b,c,d,...,y 都未用到这个变量)


有两种方式可用解决:
1、从 a 传参,一直往下传,传到 z方法,就完成了呀。(但是这样是不是太麻烦了呀,每个方法都写一个 'Date startTime' 形参来接收这个变量。)
a -> 传参 -> b -> 传参 -> c -> ... -> 传参 -> z

2、定义一个 ThreadLocal<Date> 类型的变量,在 a方法中调用 ThreadLocal 的 set(startTime) 方法,此时,因为是单线程,所以在 a到z 的整个执行链路中,都可以调用 ThreadLocal 的 get() 方法来获取到 'Date startTime' 这个当前线程私有的对象。

ThreadLocal 实际场景举例

一般来说,在用户登录请求的上下文中会设置一个 “用户id”。

1
web(request) -> 拦截器(ThreadLocal set 用户id) -> Controller(a-z方法 都可以 get() 获取 “用户id”)

InheritableThreadLocal 使用场景举例

在这里插入图片描述

先说一个这个玩意是什么。它继承了 ThreadLocal。看下面红线,可以知道,它支持线程的值继承。
在这里插入图片描述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 2022-01-10 14:41 ThreadLocal、InheritableThreadLocal 对比测试
public static void main(String[] args) throws Exception {
// [1] 主线程:初始化,并赋值
ThreadLocal<String> threadLocal = new ThreadLocal<>();
InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
threadLocal.set("I'm ThreadLocal.");
inheritableThreadLocal.set("I'm InheritableThreadLocal.");

// [2] 子线程:分别打印两个变量的信息
new Thread(() -> {
String currThreadName = Thread.currentThread().getName();
System.out.println(currThreadName + " ThreadLocal value :" + threadLocal.get());
System.out.println(currThreadName + " InheritableThreadLocal value :" + inheritableThreadLocal.get());
}, "childThread").start();
}

在这里插入图片描述

好了,我就不多说了,你们自己看。红色打印 ‘null’,绿色则打印 ‘I’m InheritableThreadLocal.’。

使用场景

如果使用 InheritableThreadLocal ,子线程1,2,3 默认都继承自主线程中的所有 ThreadLocal 值。

1
2
3
4
主线程 a -> b -> c
子线程1 c1 -> d1 -> ... -> z1
子线程2 c2 -> d2 -> ... -> z2
子线程3 c3 -> d3 -> ... -> z3

原理:
在这里插入图片描述

initialValue、childValue

2022-01-26 08:17:34 补充

  • initialValue():ThreadLocal 类型可用(ThreadLocal、InheritableThreadLocal 两种类型都支持)。
    含义:在没有 set 的时候,直接进行 get 讲道理会取出来 null。
    但如果为 null 的情况下,会调用 initialValue() 方法的返回值,做为 get获取的值(并会放入 map中)。
    在这里插入图片描述

  • childValue(T parentValue):InheritableThreadLocal 才可用(仅支持类型 InheritableThreadLocal)。
    含义:在创建 Thread 对象的时候,会 copy 父线程的 InheritableThreadLocal。如果 key 存在,调用 childValue方法。
    必要时要实现一下深克隆,因为多线程可能造成对象的修改的情况,避免造成脏数据。
    在这里插入图片描述

总结

1、ThreadLocal 主要应用与线程上下文,不用放在对象中,不用进行方法传参,只有是同一个线程,都可以随时 get() 获取这个变量。

2、InheritableThreadLocal 主要应用与开辟子线程的情况下,让此变量也存在于各个子线程的上下文中。