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
原理

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();
Runnable ttlRunnable = TtlRunnable.get(task); executorService.submit(ttlRunnable);
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();
Callable ttlCallable = TtlCallable.get(call); executorService.submit(ttlCallable);
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 = 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);
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() { final Object captured = capturedRef.get(); if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) { throw new IllegalStateException("TTL value reference is released after run!"); } final Object backup = replay(captured); try { runnable.run(); } finally { 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)