目录
本文概览:介绍InheritableThreadLocal和InheritableThreadLocal了应用场景。
0 概览
ThreadLocal、InheritableThreadLocal和InheritableThreadLocal比较:
- ThreadLocal,线程独享这个变量。
- InheritableThreadLocal ,创建子线程(new Thread)时,在子线程中 共享 父线程的threadlocal。—-谁创建 子线程, set谁的thredlocal。
- TransmittableThreadLocal 在使用线程池时,线程池的work线程 共享 调用者线程的threadlocal。 —-谁调用 work线程 ,set 谁的threadlocl
JDK的InheritableThreadLocal类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的组件的情况,线程由线程池创建好,并且work线程是池化起来反复使用的;这时父子线程关系的ThreadLocal值传递已经没有意义,应用需要的实际上是把 任务提交给线程池时的ThreadLocal值传递到 任务执行时。
注意:线程池有私有和共享情况,私有时(一个线程独享此线程池,即在这个线程中new的线程池),InheritableThreadLocal也能满足,但是一般线程池是服务的共享资源。
1 ThreadLocal
关于ThradLocal使用可以参考。
如下是一个简单实现,在介绍InheritableThreadLocal和TransmittableThreadLocal都会使用这个context。
1、定义context
1 2 3 4 5 6 7 8 9 10 11 12 |
public class Context { String productNo; public String getProductNo() { return productNo; } public void setProductNo(String productNo) { this.productNo = productNo; } } |
2、定义ContextHolder
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 |
public class ContextHolder { private static final ThreadLocal<Context> contextStore = new ThreadLocal<Context>(); /** * 获取上下文 * * @return */ public static Context getContext() { Context context = contextStore.get(); // 没有保存上下文,则返回对象 if (context == null) { return new Context(); } return context; } /** * 设置上下文 * * @param context */ public static void setContext(Context context) { contextStore.set(context); } /** * 清空 */ public static void clear() { contextStore.remove(); } } |
3、测试
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 TestContextHolder { private static final Logger LOGGER = LoggerFactory.getLogger(TestContextHolder.class); public static void main(String[] args) { // 1.主线程 Context context = new Context(); context.setProductNo("910000032"); ContextHolder.setContext(context); Thread thread1 = new Thread(new Runnable() { public void run() { LOGGER.info("new thread context,productNo="+ ContextHolder.getContext().getProductNo()); } }); // 1.启动新线程 thread1.start(); // 2.来主线程获取 LOGGER.info("main context,productNo="+ ContextHolder.getContext().getProductNo()); } } |
执行结果如下:
1 2 |
13:45:55.841 [Thread-0] INFO concurrent.TestContextHolder - new thread context,productNo=null 13:45:55.841 [main] INFO concurrent.TestContextHolder - main context,productNo=910000032 |
存在一个问题是在子线程中没有获取到context的值。此时引入了InheritableThreadLocal。
2 InheritableThreadLocal
由上面例子可以知道ThradLocal的变量不能传给子线程,此时JDK提供了InheritalbeThreadLocal,如下:
1 2 3 4 5 |
public class ContextHolder { // 使用InheritableThreadLocal类型 private static final InheritableThreadLocal<Context> contextStore = new InheritableThreadLocal<Context>(); ..... } |
此时再执行上面的TestContextHolder#main函数,输出结果如下,发现在子线程中获取到这个ThradLocal的变量值。
19:53:23.557 [pool-1-thread-1] INFO concurrent.TreadPoolContextHolder – task0,productNo=910000032
19:53:23.557 [main] INFO concurrent.TreadPoolContextHolder – main context,productNo=910000032
此时解决了子线程共享父线程的threadlocal数据的问题,但是使用线程池时会存在问题,即线程池的work线程无法获取调用线程的threadlocal变量。举例如下:
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
public class TreadPoolContextHolder { private static final Logger LOGGER = LoggerFactory.getLogger(TreadPoolContextHolder.class); // 定义线程池 ExecutorService executor = new ThreadPoolExecutor(1, 1, 300, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); private void initContext(String param) { // 1.主线程 Context context = new Context(); context.setProductNo(param); ContextHolder.setContext(context); } private void testExecute() { try { List<Future<Integer>> futureList = Lists.newArrayList(); futureList.add(executor.submit(new TaskCall(0))); } catch (Exception e) { LOGGER.error("error:", e); } } private class TaskCall implements Callable<Integer> { int p; TaskCall(int p) { this.p = p; } public Integer call() throws Exception { try { LOGGER.info("task{},productNo={}", p, ContextHolder.getContext().getProductNo()); } catch (Exception e) { } return 0; } } public static void main(String[] args) { final TreadPoolContextHolder test = new TreadPoolContextHolder(); test.initContext("t0000"); test.testExecute(); try { Thread.sleep(1000); } catch (Exception e) { } LOGGER.info("---------thread1--------"); Thread thread1 = new Thread(new Runnable() { public void run() { test.initContext("t0001"); test.testExecute(); LOGGER.info("thread 1 context,productNo=" + ContextHolder.getContext().getProductNo()); } }); // 启动新线程 thread1.start(); try { Thread.sleep(1000); } catch (Exception e) { } LOGGER.info("---------thread2--------"); Thread thread2 = new Thread(new Runnable() { public void run() { test.initContext("t0002"); test.testExecute(); LOGGER.info("thread 2 context,productNo=" + ContextHolder.getContext().getProductNo()); } }); // 启动新线程 thread2.start(); try { Thread.sleep(1000); } catch (Exception e) { } LOGGER.info("--------- main --------"); // 2.来主线程获取 LOGGER.info("main context,productNo=" + ContextHolder.getContext().getProductNo()); } } |
执行结果如下:
对于线程1和线程2,获取上下文中productNo分别为t001和t002,但是调用公用线程池中work线程的上下文是t000(这个值是创建这个work线程的 main线程设置的上下文数据)。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
## 主线程 20:18:52.784 [pool-1-thread-1] INFO concurrent.TreadPoolContextHolder - task0,productNo=t0000 ## 线程1 20:15:51.793 [Thread-0] INFO concurrent.TreadPoolContextHolder - thread 1 context,productNo=t0001 20:15:51.793 [pool-1-thread-1] INFO concurrent.TreadPoolContextHolder - task0,productNo=t0000 ## 线程2 20:15:52.788 [Thread-1] INFO concurrent.TreadPoolContextHolder - thread 2 context,productNo=t0002 20:15:52.788 [pool-1-thread-1] INFO concurrent.TreadPoolContextHolder - task0,productNo=t0000 ## 主线程 20:15:53.788 [main] INFO concurrent.TreadPoolContextHolder - main context,productNo=t0000 |
现在我们需要实现的时,在线程池中work线程执行时获取的上下文为 执行 该work线程的那个线程的上下文,不是 创建 worker线程的 那个线程的上下文。
3 TransmittableThreadLocal
3.1 引入TransmittableThreadLocal
除了设置ContextHolder中context为TransmittableThreadLocal类型,还需要自定义线程池。
1、设置context类型为TransmittableThreadLocal
1 2 3 4 |
public class ContextHolder { private static final TransmittableThreadLocal<Context> contextStore = new TransmittableThreadLocal<Context>(); .... } |
2、修饰线程池
通过如下方式修饰线程池
1 2 3 |
ExecutorService executor = TtlExecutors.getTtlExecutorService( new ThreadPoolExecutor(1, 1, 300, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>())); |
具体代码如下:
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 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
public class TreadPoolContextHolder { private static final Logger LOGGER = LoggerFactory.getLogger(TreadPoolContextHolder.class); // 使用阿里巴巴线程池 ExecutorService executor = TtlExecutors.getTtlExecutorService( new ThreadPoolExecutor(1, 1, 300, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>())); private void initContext(String param) { // 1.主线程 Context context = new Context(); context.setProductNo(param); ContextHolder.setContext(context); } private void testExecute() { try { List<Future<Integer>> futureList = Lists.newArrayList(); futureList.add(executor.submit(new TaskCall(0))); } catch (Exception e) { LOGGER.error("error:", e); } } private class TaskCall implements Callable<Integer> { int p; TaskCall(int p) { this.p = p; } public Integer call() throws Exception { try { LOGGER.info("task{},productNo={}", p, ContextHolder.getContext().getProductNo()); } catch (Exception e) { } return 0; } } public static void main(String[] args) { final TreadPoolContextHolder test = new TreadPoolContextHolder(); test.initContext("t0000"); test.testExecute(); try { Thread.sleep(1000); } catch (Exception e) { } LOGGER.info("---------thread1--------"); Thread thread1 = new Thread(new Runnable() { public void run() { test.initContext("t0001"); test.testExecute(); LOGGER.info("thread 1 context,productNo=" + ContextHolder.getContext().getProductNo()); } }); // 启动新线程 thread1.start(); try { Thread.sleep(1000); } catch (Exception e) { } LOGGER.info("---------thread2--------"); Thread thread2 = new Thread(new Runnable() { public void run() { test.initContext("t0002"); test.testExecute(); LOGGER.info("thread 2 context,productNo=" + ContextHolder.getContext().getProductNo()); } }); // 启动新线程 thread2.start(); try { Thread.sleep(1000); } catch (Exception e) { } LOGGER.info("--------- main --------"); // 2.来主线程获取 LOGGER.info("main context,productNo=" + ContextHolder.getContext().getProductNo()); } } |
执行结果为:
1 2 3 4 5 6 7 8 9 |
13:39:33.992 [pool-1-thread-1] INFO concurrent.TreadPoolContextHolder - task0,productNo=t0000 13:39:34.989 [main] INFO concurrent.TreadPoolContextHolder - ---------thread1-------- 13:39:34.991 [Thread-0] INFO concurrent.TreadPoolContextHolder - thread 1 context,productNo=t0001 13:39:34.991 [pool-1-thread-1] INFO concurrent.TreadPoolContextHolder - task0,productNo=t0001 13:39:35.996 [main] INFO concurrent.TreadPoolContextHolder - ---------thread2-------- 13:39:36.005 [Thread-1] INFO concurrent.TreadPoolContextHolder - thread 2 context,productNo=t0002 13:39:36.005 [pool-1-thread-1] INFO concurrent.TreadPoolContextHolder - task0,productNo=t0002 13:39:37.009 [main] INFO concurrent.TreadPoolContextHolder - --------- main -------- 13:39:37.009 [main] INFO concurrent.TreadPoolContextHolder - main context,productNo=t0000 |
3.2 使用时两种方式
依赖maven
1 2 3 4 5 |
<dependency> <groupId>com.alibaba</groupId> <artifactId>transmittable-thread-local</artifactId> <version>2.2.0</version> </dependency> |
使用方式如下两种,参考官网:https://github.com/alibaba/transmittable-thread-local
1、方式1 修饰线程池 (建议)
这种方式不需要再重新实现省去每次Runnable和Callable传入线程池时的修饰,通过工具类com.alibaba.ttl.threadpool.TtlExecutors完成,有下面的方法:
- getTtlExecutor:修饰接口Executor
- getTtlExecutorService:修饰接口ExecutorService。常用方式
- getTtlScheduledExecutorService:修饰接口ScheduledExecutorService
以getTtlExecutorService举例:
1 2 3 4 5 6 |
ExecutorService executorService = ... // 额外的处理,生成修饰了的对象executorService executorService = TtlExecutors.getTtlExecutorService(executorService); TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>(); parent.set("value-set-in-parent"); |
2、方式2 修饰runnable和callable
(1) 修饰runnable
1 2 3 4 5 6 7 8 9 10 11 12 |
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>(); parent.set("value-set-in-parent"); Runnable task = new Task("1"); // 额外的处理,生成修饰了的对象ttlRunnable Runnable ttlRunnable = TtlRunnable.get(task); executorService.submit(ttlRunnable); // ===================================================== // Task中可以读取,值是"value-set-in-parent" String value = parent.get(); |
(2)修饰callable
1 2 3 4 5 6 7 8 9 10 11 12 |
TransmittableThreadLocal<String> parent = new TransmittableThreadLocal<String>(); parent.set("value-set-in-parent"); Callable call = new Call("1"); // 额外的处理,生成修饰了的对象ttlCallable Callable ttlCallable = TtlCallable.get(call); executorService.submit(ttlCallable); // ===================================================== // Call中可以读取,值是"value-set-in-parent" String value = parent.get(); |
4 参考文献
1、TransmittableThreadLocal GitHub:https://github.com/alibaba/transmittable-thread-local
2、TransmittableThreadLocal 源码:https://www.jianshu.com/p/aab6b1e7357d
3、如何在子线程和线程池中使用 ThreadLocal 传输上下文 https://www.jianshu.com/p/4093add7f2cd