ThreadLocal的短板,我 TransmittableThreadLocal 来补上!(下)

简介: ThreadLocal的短板,我 TransmittableThreadLocal 来补上!(下)

小结实现思路

  • 新建一个 YesThreadLocal 类继承自 ThreadLocal ,用于标识这个修饰的变量需要父子线程拷贝
  • 新建一个 YesRunnable 类继承自 Runnable,采用装饰器模式,这样就不用修改原有的 Runnable。在构造阶段复制父线程的 YesThreadLocal 变量赋值给 YesRunnable 的一个成员变量 threadlocalCopy 保存。修饰 run 方法,在真正逻辑执行前将 threadlocalCopy 赋值给当前执行线程的上下文,且保存当前线程之前的上下文,在执行完毕之后,再复原此线程的上下文
  • 由于需要在构造的时候复制所有父线程用到的 YesThreadLocal ,因此需要有个 holder 变量来保存所有用到的 YesThreadLocal ,这样在构造的时候才好遍历赋值。并且 holder 变量也需要线程隔离,所以用 ThreadLocal 修饰,并且为了防止 holder 强引用导致内存泄漏,所以用 WeakHashMap 存储。
  • 往 holder 添加 YesThreadLocal 的时机就在 YesThreadLocal#set 之时


TransmittableThreadLocal 的实现


这篇只讲 TTL 核心思想(关键路径),由于篇幅原因其它的不作展开,之后再写一篇详细的。

我上面的实现其实就是 TTL 的复制版,如果你理解了上面的实现,那么接下来对 TTL 介绍理解起来应该很简单,相当于复习了。

我们先简单看一下 TTL 的使用方式。

image.png


使用起来很简单对吧?

TTL 对标上面的 YesThreadLocal ,差别在于它继承的是 InheritableThreadLocal,因为这样直接 new TTL 也会拥有父子线程本地变量的传递能力。


image.png


所以,在父线程赋值即执行 set 操作之后,父线程里的 holder 就存储了当前的 TTL 对象了,即上面演示代码的 ttl.set() 操作。

然后重点就移到了TtlRunnable.get 上了,根据上面的理解我们知道这里是要进行一个装饰的操作,这个 get 代码也比较简单,核心就是 new 一个 TtlRunnable 包装了原始的 task。


image.png


这个 capturedRef 其实就是父线程本地变量的拷贝,然后 capture() 其实就等同于copyFatherThreadlocal()

再来看一下 TtlRunnable 装饰的 run 方法:


image.png


逻辑很清晰的四步骤:

  1. 拿到父类本地变量拷贝
  2. 赋值给当前线程(线程池内的某线程),并保存之前的本地变量
  3. 执行逻辑
  4. 复原当前线程之前的本地变量

我们再来分析一下 capture() 方法,即如何拷贝的。

在 TTL 中是专门定义了一个静态工具类 Transmitter 来实现上面的 capture、 replay、restore 操作。


image.png


可以看到 capture 的逻辑其实就是返回一个快照,而这个快照就是遍历 holder 获取所有存储在 holder 里面的 TTL ,返回一个新的 map,还是很简单的吧!

这里还有个 captureThreadLocalValues ,这个是为兼容那些无法将 ThreadLocal 类变更至 TTL ,但是又想复制传递 ThreadLocal 的值而使用的,可以先忽略。

我们再来看看 replay,即如何将父类的本地变量赋值给当前线程的。


image.png


就是 for 循环进行了一波 set ,从这里也可以得知为什么上面需要移除父线程没有的 TTL,因为这里只是进行了 set。如果不 remove 当前线程的本地变量,那就不是完全继承自父线程的本地变量了,可能掺杂着之前的本地变量,也就是不干净了,防止这种干扰,所以还是 remove 了为妙。

最后我们看下 restore 操作:


image.png


至此想必对 TTL 的原理应该都很清晰了吧!


一些用法


上面我们展示的只是其中一个用法也就是利用 TtlRunnable.get 来包装 Runnable。

TTL 还提供了线程池的修饰方法,即 TtlExecutors,比如可以这样使用:

ExecutorService executorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));

其实原理也很简单,装饰了一下线程池提交任务的方法,里面实现了 TtlRunnable.get 的包装


image.png


还有一种使用方式更加透明,即利用 Java Agent 来修饰 JDK 的线程池实现类,这种方式在使用上基本就是无感知了。

在 Java 的启动参数加上:-javaagent:path/to/transmittable-thread-local-2.x.y.jar 即可,然后就正常的使用就行,原生的线程池实现类已经悄悄的被改了!

TransmittableThreadLocal<String> ttl= new TransmittableThreadLocal<>();
ExecutorService executorService = Executors.newFixedThreadPool(1);
Runnable task = new RunnableTask();
executorService.submit(task);


最后


好了,有关 TTL 的原理和用法解释的都差不多了。

总结下来的核心操作就是 CRR(Capture/Replay/Restore),拷贝快照、重放快照、复原上下文。

可能有些人会疑惑为什么需要复原,线程池的线程每次执行的时候,如果用了 TTL 那执行的线程都会被覆盖上下文,没必要复原对吧?

其实也有人向作者提了这个疑问,回答是:

  • 线程池满了且线程池拒绝策略使用的是『CallerRunsPolicy』,这样执行的线程就变成当前线程了,那肯定是要复原的,不然上下文就没了。
  • 使用ForkJoinPool(包含并行执行Stream与CompletableFuture,底层使用ForkJoinPool)的场景,展开的ForkJoinTask会在调用线程中直接执行。

其实关于 TTL 还有很多细节可以说,不过篇幅有限,细节要说的话得再开一章。不过今天这篇也算把 TTL 的核心思想讲完了。

假设现在有个面试官问你,我要向线程池里面传递 ThreadLocal 怎么实现呀?想必你肯定可以回答出来了~

好了,后面有时间我再出一篇 TTL 的细节~等我哈。

欢迎关注我的公众号【yes的练级攻略】,更多文章,等你来阅!


相关文章
|
5月前
|
SQL 关系型数据库 MySQL
MySQL进阶突击系列(07) 她气鼓鼓递来一条SQL | 怎么看执行计划、SQL怎么优化?
在日常研发工作当中,系统性能优化,从大的方面来看主要涉及基础平台优化、业务系统性能优化、数据库优化。面对数据库优化,除了DBA在集群性能、服务器调优需要投入精力,我们研发需要负责业务SQL执行优化。当业务数据量达到一定规模后,SQL执行效率可能就会出现瓶颈,影响系统业务响应。掌握如何判断SQL执行慢、以及如何分析SQL执行计划、优化SQL的技能,在工作中解决SQL性能问题显得非常关键。
|
6月前
|
Java 关系型数据库 MySQL
MySQL 分库分表方案
本文总结了数据库分库分表的相关概念和实践,针对单张表数据量过大及增长迅速的问题,介绍了垂直和水平切分的方式及其适用场景。文章分析了分库分表后可能面临的事务支持、多库结果集合并、跨库join等问题,并列举了几种常见的开源分库分表中间件。最后强调了不建议水平分库分表的原因,帮助读者在规划时规避潜在问题。
880 20
|
存储 安全 前端开发
微服务中使用阿里开源的TTL,优雅的实现身份信息的线程间复用
微服务中使用阿里开源的TTL,优雅的实现身份信息的线程间复用
|
easyexcel
easyExcel自定定义类型转换
easyExcel自定定义类型转换
543 0
|
9月前
|
存储 Java 测试技术
一文彻底搞懂阿里开源TransmittableThreadLocal的原理和使用
【10月更文挑战第2天】在Java多线程编程中,线程本地变量(ThreadLocal)是一个非常有用的工具,它能够在每个线程中保存一个独立的变量副本,从而避免多线程环境下的数据竞争问题。然而,在使用线程池等高级多线程技术时,ThreadLocal却面临着一些挑战。为了解决这个问题,阿里巴巴开源了TransmittableThreadLocal(TTL),它扩展了ThreadLocal的功能,使其能够在复杂的多线程环境中正确传递值。本文将深入探讨TTL的原理和使用,帮助读者彻底理解这一技术干货。
1260 0
|
存储 设计模式 Java
ThreadLocal的短板,我 TransmittableThreadLocal 来补上!(上)
ThreadLocal的短板,我 TransmittableThreadLocal 来补上!(上)
ThreadLocal的短板,我 TransmittableThreadLocal 来补上!(上)
|
Linux 网络安全 数据安全/隐私保护
|
NoSQL Redis 数据库
Redis的GUI工具——Another-Redis-Desktop-Manager连接远程数据库Redis
Redis的GUI工具——Another-Redis-Desktop-Manager连接远程数据库Redis
5753 0
|
安全 Java 开发者
JDK 9:模块化系统——重新定义Java的模块化架构
JDK 9引入了模块化系统,对Java的模块化架构进行了彻底的重新定义。本文将深入探讨模块化系统的原理、优势以及如何在实际开发中应用这一特性。