1. TransmittableThreadLocal 怎么用?

1.1 ThreadLocal使用

子线程获取不到 父线程set到ThreadLocal所修饰变量的值;

1
2
3
4
5
6
7
8
9
10
11
12
public class TtlTest {

@Test
public void Test001() {
ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
stringThreadLocal.set("主线程给的值:stringThreadLocal");
Thread thread = new Thread(() -> {
System.out.println("读取父线程stringThreadLocal的值:" + stringThreadLocal.get());
});
thread.start();
}
}
1
读取父线程stringThreadLocal的值:null

1.2 InheritableThreadLocal使用

子线程能获取到 父线程set到InheritableThreadLocal所修饰变量的值;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TtlTest {

@Test
public void Test002() {
ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

stringThreadLocal.set("主线程给的值:stringThreadLocal");
inheritableThreadLocal.set("主线程给的值:inheritableThreadLocal");
Thread thread = new Thread(() -> {
System.out.println("读取父线程stringThreadLocal的值:" + stringThreadLocal.get());
System.out.println("读取父线程inheritableThreadLocal的值:" + inheritableThreadLocal.get());
});
thread.start();
}
}
1
2
读取父线程stringThreadLocal的值:null
读取父线程inheritableThreadLocal的值:主线程给的值:inheritableThreadLocal

1.3 InheritableThreadLocal线程池场景下使用

当使用线程池时,因为线程都是复用的,在子线程中获取父线程的值,可能获取出来的是上一个线程 的值,所以这里会有线程安全问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TtlTest {

@Test
public void Test003() {
ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

ThreadPoolExecutor executor = new ThreadPoolExecutor(
5,
5,
60,
TimeUnit.SECONDS, new LinkedBlockingQueue<>()
);

for (int i = 0; i < 10; i++) {
String val = "主线程给的值:inheritableThreadLocal:"+i;
System.out.println("主线程set;"+val);
inheritableThreadLocal.set(val);
executor.execute(()->{
System.out.println("线程池:读取父线程 inheritableThreadLocal 的值:" + inheritableThreadLocal.get()); });
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
主线程set;主线程给的值:inheritableThreadLocal:0
主线程set;主线程给的值:inheritableThreadLocal:1
线程池:读取父线程 inheritableThreadLocal 的值:主线程给的值:inheritableThreadLocal:0
主线程set;主线程给的值:inheritableThreadLocal:2
线程池:读取父线程 inheritableThreadLocal 的值:主线程给的值:inheritableThreadLocal:1
主线程set;主线程给的值:inheritableThreadLocal:3
线程池:读取父线程 inheritableThreadLocal 的值:主线程给的值:inheritableThreadLocal:2
主线程set;主线程给的值:inheritableThreadLocal:4
线程池:读取父线程 inheritableThreadLocal 的值:主线程给的值:inheritableThreadLocal:3
主线程set;主线程给的值:inheritableThreadLocal:5
线程池:读取父线程 inheritableThreadLocal 的值:主线程给的值:inheritableThreadLocal:4
主线程set;主线程给的值:inheritableThreadLocal:6
线程池:读取父线程 inheritableThreadLocal 的值:主线程给的值:inheritableThreadLocal:0
主线程set;主线程给的值:inheritableThreadLocal:7
线程池:读取父线程 inheritableThreadLocal 的值:主线程给的值:inheritableThreadLocal:4
线程池:读取父线程 inheritableThreadLocal 的值:主线程给的值:inheritableThreadLocal:2
主线程set;主线程给的值:inheritableThreadLocal:8
主线程set;主线程给的值:inheritableThreadLocal:9
线程池:读取父线程 inheritableThreadLocal 的值:主线程给的值:inheritableThreadLocal:3
线程池:读取父线程 inheritableThreadLocal 的值:主线程给的值:inheritableThreadLocal:1

1.4 TransmittableThreadLocal使用

TransmittableThreadLocal可以解决线程池中复用线程时,将值传递给实际执行业务的线程,解决异步执行时的上下文传递问题。

但是真的没问题吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class TtlTest {

@Test
public void Test004() {
ThreadLocal<String> transmittableThreadLocal = new TransmittableThreadLocal<>();

ThreadPoolExecutor executor = new ThreadPoolExecutor(
5,
5,
60,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>()
);

for (int i = 0; i < 10; i++) {
String val = "主线程给的值:TransmittableThreadLocal:"+i;
System.out.println("主线程set3;"+val);
transmittableThreadLocal.set(val);
executor.execute(TtlRunnable.get(()->{
System.out.println("线程池线程:"+Thread.currentThread().getName()+ "读取父线程 TransmittableThreadLocal 的值:" + transmittableThreadLocal.get());
}));
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
主线程set3;主线程给的值:TransmittableThreadLocal:0
主线程set3;主线程给的值:TransmittableThreadLocal:1
主线程set3;主线程给的值:TransmittableThreadLocal:2
主线程set3;主线程给的值:TransmittableThreadLocal:3
线程池线程:pool-1-thread-2读取父线程 TransmittableThreadLocal 的值:主线程给的值:TransmittableThreadLocal:1
主线程set3;主线程给的值:TransmittableThreadLocal:4
线程池线程:pool-1-thread-1读取父线程 TransmittableThreadLocal 的值:主线程给的值:TransmittableThreadLocal:0
线程池线程:pool-1-thread-3读取父线程 TransmittableThreadLocal 的值:主线程给的值:TransmittableThreadLocal:2
线程池线程:pool-1-thread-4读取父线程 TransmittableThreadLocal 的值:主线程给的值:TransmittableThreadLocal:3
主线程set3;主线程给的值:TransmittableThreadLocal:5
线程池线程:pool-1-thread-5读取父线程 TransmittableThreadLocal 的值:主线程给的值:TransmittableThreadLocal:4
线程池线程:pool-1-thread-2读取父线程 TransmittableThreadLocal 的值:主线程给的值:TransmittableThreadLocal:5
主线程set3;主线程给的值:TransmittableThreadLocal:6
主线程set3;主线程给的值:TransmittableThreadLocal:7
线程池线程:pool-1-thread-1读取父线程 TransmittableThreadLocal 的值:主线程给的值:TransmittableThreadLocal:6
主线程set3;主线程给的值:TransmittableThreadLocal:8
线程池线程:pool-1-thread-3读取父线程 TransmittableThreadLocal 的值:主线程给的值:TransmittableThreadLocal:7
主线程set3;主线程给的值:TransmittableThreadLocal:9
线程池线程:pool-1-thread-3读取父线程 TransmittableThreadLocal 的值:主线程给的值:TransmittableThreadLocal:8
线程池线程:pool-1-thread-5读取父线程 TransmittableThreadLocal 的值:主线程给的值:TransmittableThreadLocal:9

2. TransmittableThreadLocal 中的深拷贝

ThreadLocal 存储的是对象时,父子线程共享同一个对象。

也就是说父子线程之间的修改都是可见的,原因就是父子线程持有的 Map 都是同一个,在父线程第二次设置值的时候,因为修改的都是同一个 Map,所以子线程也可以读取到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class TtlTest {

@Test
public void Test005() {
ThreadLocal<Map<String,Object>> transmittableThreadLocal = new TransmittableThreadLocal<>();

ThreadPoolExecutor executor = new ThreadPoolExecutor(
5,
5,
60,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>()
);

Map<String, Object> map = new HashMap<>();
map.put("mainThread","主线程给的值:main");
System.out.println("主线程赋值:"+ map);
transmittableThreadLocal.set(map);
executor.execute(TtlRunnable.get(()->{
System.out.println("线程池线程:"+Thread.currentThread().getName()+ "读取父线程 TransmittableThreadLocal 的值:" + transmittableThreadLocal.get());
}));
}
}
1
2
主线程赋值:{mainThread=主线程给的值:main}
线程池线程:pool-1-thread-1读取父线程 TransmittableThreadLocal 的值:{mainThread=主线程给的值:main}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
@RestController
@RequestMapping("/test2")
public class Test2Controller {

ThreadLocal<Map<String, Object>> transmittableThreadLocal = new TransmittableThreadLocal<>();
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

@RequestMapping("/set")
public Object set() {
Map<String, Object> map = transmittableThreadLocal.get();
if (null == map) {
map = new HashMap<>();}
map.put("mainThread", "主线程给的值:main");
System.out.println("主线程赋值:" + map);
transmittableThreadLocal.set(map);
executor.execute(TtlRunnable.get(() -> {
System.out.println("子线程输出:" + Thread.currentThread().getName() + "读取父线程 TransmittableThreadLocal 的值:" + transmittableThreadLocal.get());
Map<String, Object> childMap = transmittableThreadLocal.get();
if (null == childMap){childMap = new HashMap<>();}
childMap.put("childThread","子线程添加值");
}));
Map<String, Object> stringObjectMap = transmittableThreadLocal.get();
if (null == stringObjectMap) {
stringObjectMap = new HashMap<>();
}
stringObjectMap.put("mainThread-2", "主线程第二次赋值");
transmittableThreadLocal.set(stringObjectMap);
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("主线程第二次输出ThreadLocal:"+transmittableThreadLocal.get());
return "";
}
}
1
2
3
主线程赋值:{mainThread=主线程给的值:main}
子线程输出:pool-1-thread-1读取父线程 TransmittableThreadLocal 的值:{mainThread=主线程给的值:main, mainThread-2=主线程第二次赋值}
主线程第二次输出ThreadLocal:{mainThread=主线程给的值:main, mainThread-2=主线程第二次赋值, childThread=子线程添加值}

如何解决呢?深拷贝,对象的深拷贝,保证父子线程独立,在修改的时候就不会出现父子线程共享同一个对象的事情。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class TtlTest {

@Test
public void Test007() {
ThreadLocal<Map<String, Object>> transmittableThreadLocal = new TransmittableThreadLocal(){
@Override
public Object copy(Object parentValue) {
return new HashMap<>((Map)parentValue);
}
};
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

Map<String, Object> map = transmittableThreadLocal.get();
if (null == map) {map = new HashMap<>();}
map.put("mainThread", "主线程给的值:main");
System.out.println("主线程赋值:" + map);
transmittableThreadLocal.set(map);
executor.execute(TtlRunnable.get(() -> {
System.out.println("子线程输出:" + Thread.currentThread().getName() + "读取父线程 TransmittableThreadLocal 的值:" + transmittableThreadLocal.get());
Map<String, Object> childMap = transmittableThreadLocal.get();
if (null == childMap) {
childMap = new HashMap<>();
}
childMap.put("childThread","子线程添加值");
}));
Map<String, Object> stringObjectMap = transmittableThreadLocal.get();
if (null == stringObjectMap) {
stringObjectMap = new HashMap<>();
}
stringObjectMap.put("mainThread-2", "主线程第二次赋值");
transmittableThreadLocal.set(stringObjectMap);
try{
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("主线程第二次输出ThreadLocal:"+transmittableThreadLocal.get());

}
}
1
2
3
主线程赋值:{mainThread=主线程给的值:main}
子线程输出:pool-1-thread-1读取父线程 TransmittableThreadLocal 的值:{mainThread=主线程给的值:main}
主线程第二次输出ThreadLocal:{mainThread=主线程给的值:main, mainThread-2=主线程第二次赋值}

3. TransmittableThreadLocal 原理

3.1 使用方式

时序图

1、修饰Runnable或者Callable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();

// =====================================================

// 在父线程中设置
context.set("value-set-in-parent");

Runnable task = new RunnableTask();
// 额外的处理,生成修饰了的对象ttlRunnable
Runnable ttlRunnable = TtlRunnable.get(task);
executorService.submit(ttlRunnable);

// =====================================================

// Task中可以读取,值是"value-set-in-parent"
String value = context.get();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();

// =====================================================

// 在父线程中设置
context.set("value-set-in-parent");

Callable call = new CallableTask();
// 额外的处理,生成修饰了的对象ttlCallable
Callable ttlCallable = TtlCallable.get(call);
executorService.submit(ttlCallable);

// =====================================================

// Call中可以读取,值是"value-set-in-parent"
String value = context.get();

2、使用TtlExecutors修饰线程池

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ExecutorService executorService = ...
// 额外的处理,生成修饰了的对象executorService
executorService = TtlExecutors.getTtlExecutorService(executorService);

TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();

// =====================================================

// 在父线程中设置
context.set("value-set-in-parent");

Runnable task = new RunnableTask();
Callable call = new CallableTask();
executorService.submit(task);
executorService.submit(call);

// =====================================================

// Task或是Call中可以读取,值是"value-set-in-parent"
String value = context.get();

3、使用Java Agent来修饰JDK线程池实现类

3.2 源码分析

先简单的概括下:

1、修饰 Runnable ,将主线程的 TTL 值传入到 TtlRunnable 的构造方法中。

2、将子线程的 TTL 进行备份,主线程的值设置到子线程中。

3、子线程执行业务逻辑。

4、删除子线程新增的 TTL,将备份重新设置到子线程中

3.2.1 TtlRunnable#run 方法做了什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
public void run() {
// 1. 快照
final Object captured = capturedRef.get();
if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
throw new IllegalStateException("TTL value reference is released after run!");
}
// 2. 回放
// 具体来讲:将使用1返回的父线程ttl的快照设置到子线程,并将子线程的ttl返回
final Object backup = replay(captured);
try {
runnable.run();
} finally {
// 3. 恢复
restore(backup);
}
}

3.2.2 captured 快照是什么时候做的

com.alibaba.ttl.TtlRunnable#get(java.lang.Runnable) ->

3.2.3 holder 中在哪赋值的

3.2.4 replay 备份与回放数据

3.2.5 restore 恢复

4. TransmittableThreadLocal的初始化方法

4.1 ThreadLocal#initialValue()

4.2 InheritableThreadLocal#childValue(T)

4.3 TransmittableThreadLocal#copy(T)


本站由 卡卡龙 使用 Stellar 1.29.1主题创建

本站访问量 次. 本文阅读量 次.