上一篇文章中,我们谈到了 InheritableThreadLocal,它解决了 ThreadLocal 针对父子线程无法共享上下文
的问题。但我们可能听说过阿里的开源产品TransmittableThreadLocal
,那么它又是做什么的呢?
线程池中的共享
我们在多线程中,很少会直接 new 一个线程,更多的可能是利用线程池处理任务,那么利用 InheritableThreadLocal 可以将生成任务线程的上下文传递给执行任务的线程吗?废话不多说,直接上代码测试一下:
1 | public class InheritableThreadLocalContext { |
我们希望的结果是,子线程输出的内容能够和父线程对应上。然而,实际的结果却出乎所料,我将结果整理一下:
1 | Thread1 produce task 21 |
虽然生产总数和消费总数都是100,但是明显有的消费多了,有的消费少了。合理推测一下,应该是在主线程放进任务后,子线程才生成。为了验证这个猜想,将线程池用 ThreadPoolExecutor 生成,并在用子线程生成任务之前,先赋值 context 并开启所有线程:
1 | public static void main(String[] args) { |
结果不出所料,执行任务的线程输出的,都是最外面主线程设置的值。
那么我们该如何才能达到最初想要的效果呢?就是利用线程池执行任务时,如何能够让执行者线程能够获取调用者线程的 context 呢?
使用 TransmittableThreadLocal 解决
上面的问题主要是因为执行任务的线程是被线程池管理,可以被复用(可以称为池化复用
)。那复用了之后,如果还是依赖于父线程的 context,自然是有问题的,因为我们想要的效果是执行线程获取调用线程的 context,这时候就是TransmittableThreadLocal
出场了。
TransmittableThreadLocal 是阿里提供的工具类,其主要解决的就是上面遇到的问题。那么该如何使用呢?
首先,你需要引入相应的依赖:
1 | <dependency> |
具体代码,就拿上文提到的情况,我们用 TransmittableThreadLocal 做一个改造:
1 | public class TransmittableThreadLocalTest { |
此时再次运行,就会发现执行线程运行时的输出内容是完全可以和调用线程对应上的了。当然了,我这种方式是修改了 Runnable 的写法,阿里也提供了线程池的写法,简单如下:
1 | public static void main(String[] args) { |
其实还有更加简单的写法,具体可以参考其github:https://github.com/alibaba/transmittable-thread-local
总结
其实两篇 ThreadLocal 升级文章的出现,都是因为周三听了一个部门关于 TTL 的分享会,也是介绍了 TransmittableThreadLocal,但因为携程商旅面临国际化的改动,当前的语种信息肯定是存储在线程的 context 中最方便,但涉及到线程传递的问题(因为会调用异步接口等等),所以自然就需要考虑这个了。性能方面的话,他们有做过测试,但我也只是一个听者,并没有具体使用过,大家也可以一起交流。
有兴趣的话可以访问我的博客或者关注我的公众号、头条号,说不定会有意外的惊喜。
公众号:健程之道