为了账号安全,请及时绑定邮箱和手机立即绑定

ThreadLocal的短板,我TTL来补

标签:
AngularJS

之前我已经分析了ThreadLocal、InheritableThreadLocal、FastThreadLocal。

然后有小伙伴让我再说说TransmittableThreadLocal(下边统一简称:TTL),它是阿里开源的一个工具类,解决异步执行时上下文传递的问题。

那今天就来介绍介绍 TTL,补充下 ThreadLocal 家族的短板吧。

这篇过后,ThreadLocal 就真的一网打尽了!

不过还是建议先看看前置篇(文末会放链接),不然理解起来可能有点困难。

缘由

任何一个组件的出现必有其缘由,知其缘由背景才能更深刻地理解它。

我们知道 ThreadLocal 的出现就是为了本地化线程资源,防止不必要的多线程之间的竞争。

在有些场景,当父线程 new 一个子线程的时候,希望把它的 ThreadLocal 继承给子线程。

这时候 InheritableThreadLocal 就来了,它就是为了父子线程传递本地化资源而提出的。

具体的实现是在子线程对象被 new 的时候,即 Thread.init 的时,如果查看到父线程内部有 InheritableThreadLocal 的数据。

那就在子 Thread 初始化的时,把父线程的 InheritableThreadLocal 拷贝给子线程。

ThreadLocal的短板,我TTL来补

就这样简单地把父线程的 ThreadLocal 数据传递给子线程了。

但是,这个场景只能发生在 new Thread 的时候!也就是手动创建线程之时!那就有个问题了,在平时我们使用的时候基本用的都是线程池。

那就麻了啊,线程池里面的线程都预创建好了,调用的时候就没法直接用 InheritableThreadLocal 了。

所以就产生了一个需求,如何往线程池内的线程传递 ThreadLocal?,JDK 的类库没这个功能,所以怎么搞?

只能我们自己造轮子了。

如何设计?

需求已经明确了,但是怎么实现呢?

平时我们用线程池的话,比如你要提交任务,则使用代码如下:

Runnable task=new Runnable…;

executorService.submit(task);

小贴士:以下的 ThreadLocal 泛指线程本地数据,不是指 ThreadLocal 这个类

这时候,我们想着把当前线程的 ThreadLocal 传递给线程池内部将要执行这个 task 的线程。

但此时我们哪知道线程池里面的哪个线程会来执行这个任务?

所以,我们得先把当前线程的 ThreadLocal 保存到这个 task 中。

然后当线程池里的某个线程,比如线程 A 获取这个任务要执行的时候,看看 task 里面是否有存储着的 ThreadLocal 。

如果存着那就把这个 ThreadLocal 放到线程 A 的本地变量里,这样就完成了传递。

然后还有一步,也挺关键的,就是恢复线程池内部执行线程的上下文,也就是该任务执行完毕之后,把任务带来的本地数据给删了,把线程以前的本地数据复原。

ThreadLocal的短板,我TTL来补

设计思路应该已经很明确了吧?来看看具体需要如何实现吧!

如何实现?

把上面的设计简单地、直白地翻译成代码如下:

ThreadLocal的短板,我TTL来补

如果你读过我之前分析 ThreadLocal 的文章,应该可以很容易的理解上面的操作。

这样虽然可以实现,但是游戏下载可操作性太差,耦合性太高。

所以我们得想想怎么优化一下,其实有个设计模式就很合适,那就是装饰器模式。

我们可以自己搞一个 Runnable 类,比如 YesRunnable,然后在 new YesRunnable 的时候,在构造器里面把当前线程的 threadlocal 赋值进去。

然后 run 方法那里也修饰一下,我们直接看看伪代码:

public YesRunnable(Runnable runable) {

this.threadlocalCopy=copyFatherThreadlocal();

thisable=runable;

}

public void run() {

//塞入父threadlocal,并返回当前线程原先threadlocal

Object backup=setThreadlocal(threadlocalCopy);

try {

runable();//执行被装饰的任务逻辑

} finally {

restore(backup) ; //复原当前线程的上下文

}

}

使用方式如下:

Runnable task=() -> {…};

YesRunnable yesRunnable=new YesRunnable(task);

executorService.submit(yesRunnable);

你看,这不就实现我们上面的设计了嘛!

不过还有一个点没有揭秘,就是如何实现 copyFatherThreadlocal。

我们如何得知父线程现在到底有哪些 Threadlocal?并且哪些是需要上下文传递的?

所以我们还需要创建一个类来继承 Threadlocal。

比如叫 YesThreadlocal,用它声明的变量就表明需要父子传递的!

public class YesThreadlocal extends ThreadLocal

然后我们需要搞个地方来存储当前父线程上下文用到的所有 YesThreadlocal,这样在 copyFatherThreadlocal的时候我们才好遍历复制对吧?

我们可以搞个 holder 来保存这些 YesThreadlocal ,不过 holder 变量也得线程隔离。

毕竟每个线程所要使用的 YesThreadlocal 都不一样,所以需要用 ThreadLocal 来修饰 holder 。

然后 YesThreadlocal 可能会有很多,我们可以用 set 来保存。

但是为了防止我们搞的这个 holder 造成内存泄漏的风险,我们需要弱引用它,不过没有 WeakHashSet,那我们就用 WeakHashMap 来替代存储。

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消