0


TransmittableThreadLocal详解

文章目录

介绍

TransmittableThreadLocal(TTL)

是阿里开源的用于解决,在使用线程池等会池化复用线程的执行组件情况下,提供ThreadLocal值的传递功能,解决异步执行时上下文传递的问题。
详细的内容可以查看 https://github.com/alibaba/transmittable-thread-local

需要解决的问题

通过上一篇文章我们了解到

ThreadLocal

Inheritable

所存在的局限性,针对这种局限性

TTL

提出并实现了一种解决方案。

如果对于

TTL

和上面所说的局限性,没有清晰概念的同学可以看下ThreadLocal与InheritableThreadLocal的实现原理和上面

TTL

github上面的介绍

在探究源码之前,我们需要明确使用的场景,以及场景所产生的问题。

  1. 在线程之中建立或使用另一个线程,并且需要继承当前线程的上下文
  2. 建立或使用线程,存在两种情况,第一立即使用,第二某段时间后使用(线程池提交但不马上执行)
  3. 执行任务的线程也存在两种情况,第一新线程(其它线程),第二当前线程(由池化特性决定)

对于第三种情况,以线程池为例来说,如果拒绝策略为CallerRunsPolicy,也就是用提交的线程来执行,那么就存在第二种情况,由当前线程执行

#mermaid-svg-jI0Rcokz7nauXKWE {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-jI0Rcokz7nauXKWE .error-icon{fill:#552222;}#mermaid-svg-jI0Rcokz7nauXKWE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jI0Rcokz7nauXKWE .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-jI0Rcokz7nauXKWE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jI0Rcokz7nauXKWE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jI0Rcokz7nauXKWE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jI0Rcokz7nauXKWE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jI0Rcokz7nauXKWE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jI0Rcokz7nauXKWE .marker.cross{stroke:#333333;}#mermaid-svg-jI0Rcokz7nauXKWE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jI0Rcokz7nauXKWE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jI0Rcokz7nauXKWE .cluster-label text{fill:#333;}#mermaid-svg-jI0Rcokz7nauXKWE .cluster-label span{color:#333;}#mermaid-svg-jI0Rcokz7nauXKWE .label text,#mermaid-svg-jI0Rcokz7nauXKWE span{fill:#333;color:#333;}#mermaid-svg-jI0Rcokz7nauXKWE .node rect,#mermaid-svg-jI0Rcokz7nauXKWE .node circle,#mermaid-svg-jI0Rcokz7nauXKWE .node ellipse,#mermaid-svg-jI0Rcokz7nauXKWE .node polygon,#mermaid-svg-jI0Rcokz7nauXKWE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jI0Rcokz7nauXKWE .node .label{text-align:center;}#mermaid-svg-jI0Rcokz7nauXKWE .node.clickable{cursor:pointer;}#mermaid-svg-jI0Rcokz7nauXKWE .arrowheadPath{fill:#333333;}#mermaid-svg-jI0Rcokz7nauXKWE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jI0Rcokz7nauXKWE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jI0Rcokz7nauXKWE .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-jI0Rcokz7nauXKWE .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-jI0Rcokz7nauXKWE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jI0Rcokz7nauXKWE .cluster text{fill:#333;}#mermaid-svg-jI0Rcokz7nauXKWE .cluster span{color:#333;}#mermaid-svg-jI0Rcokz7nauXKWE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jI0Rcokz7nauXKWE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}
当前线程

立即执行?

立即执行

延迟执行

新线程执行

当前线程执行

接下来,梳理下可能面临的问题点(以线程池为例)

  1. 何时进行当前线程上下文的获取? 由于上面的场景存在延时执行,那么获取上下文就只能在新线程创建的时候,对于使用其它线程(线程池存在的线程)就只能是创建任务的时候。

ps:对线程池理解不太明白的同学,可以看看我另一篇文章
ThreadPoolExecutor源码详解

  1. 如何拷贝上下文? 对于引用对象来说,如果直接使用其地址,可能就存在问题,外层会影响到执行线程的信息,这需要根据业务场景来确定,是否能影响。
  2. 对于当前线程执行的情况,如何保证上下文不丢失? 这种情况出现在,当我们提交的任务被划分的线程有自己的上下文(任务的提交和实际执行中间存在时间差,如果这个时间段出现了上下文的更新,那么直接覆盖将导致本次更新丢失),那么就需要保证在任务执行的时候是当时的上下文,执行完毕后需要还原。
  3. 什么时候设置上下文? 由于前面我们知道,在任务提交和执行存在一定的时间差,那么设置上下文的时候,就不能是创建的时候,只能是在执行之前(如果在创建的时候,还需要考虑,中途如果没轮到该任务执行就设置了上下文,线程如果还有其它的流程需要执行,就会导致上下文丢失问题)

最后,我们尝试画下时序图(以线程池为例,最常规的情况)

#mermaid-svg-N35Yo687mWfKlE0w {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-N35Yo687mWfKlE0w .error-icon{fill:#552222;}#mermaid-svg-N35Yo687mWfKlE0w .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-N35Yo687mWfKlE0w .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-N35Yo687mWfKlE0w .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-N35Yo687mWfKlE0w .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-N35Yo687mWfKlE0w .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-N35Yo687mWfKlE0w .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-N35Yo687mWfKlE0w .marker{fill:#333333;stroke:#333333;}#mermaid-svg-N35Yo687mWfKlE0w .marker.cross{stroke:#333333;}#mermaid-svg-N35Yo687mWfKlE0w svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-N35Yo687mWfKlE0w .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-N35Yo687mWfKlE0w text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-N35Yo687mWfKlE0w .actor-line{stroke:grey;}#mermaid-svg-N35Yo687mWfKlE0w .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-N35Yo687mWfKlE0w .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-N35Yo687mWfKlE0w #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-N35Yo687mWfKlE0w .sequenceNumber{fill:white;}#mermaid-svg-N35Yo687mWfKlE0w #sequencenumber{fill:#333;}#mermaid-svg-N35Yo687mWfKlE0w #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-N35Yo687mWfKlE0w .messageText{fill:#333;stroke:#333;}#mermaid-svg-N35Yo687mWfKlE0w .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-N35Yo687mWfKlE0w .labelText,#mermaid-svg-N35Yo687mWfKlE0w .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-N35Yo687mWfKlE0w .loopText,#mermaid-svg-N35Yo687mWfKlE0w .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-N35Yo687mWfKlE0w .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-N35Yo687mWfKlE0w .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-N35Yo687mWfKlE0w .noteText,#mermaid-svg-N35Yo687mWfKlE0w .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-N35Yo687mWfKlE0w .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-N35Yo687mWfKlE0w .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-N35Yo687mWfKlE0w .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-N35Yo687mWfKlE0w .actorPopupMenu{position:absolute;}#mermaid-svg-N35Yo687mWfKlE0w .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-N35Yo687mWfKlE0w .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-N35Yo687mWfKlE0w .actor-man circle,#mermaid-svg-N35Yo687mWfKlE0w line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-N35Yo687mWfKlE0w :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}
当前线程

   线程池 
 
   新线程(其它线程) 
 
1.提交任务Runable(拷贝当前上下文) 

2. 等待执行 

3. 执行任务 

4. 将当前线程上下文保存一份(看问题3) 

5. 将拷贝的上下文设置到TTL中 

6. run(),执行任务通过TTL获取上下文 

7. 将4中保存重新设置到TTL中 

  当前线程 

  线程池 

  新线程(其它线程) 

在对整体流程有了详细理解后,接下来就进行源码阅读

源码

对于如何使用,可以查看官网中的使用方式,使用方式是比较简单的。

TTL整体是通过装饰器模式,来对现有的线程池,Runable进行增强。

最简单的使用方式:

//使用TTLTransmittableThreadLocal<Map<String,Integer>>USER_CONTEXT=newTransmittableThreadLocal<>();//将普通的Runable包装成TtlRunnableTtlRunnable ttlRunnable =TtlRunnable.get(()->{System.out.println(USER_CONTEXT.get().get("username"));});newThread(ttlRunnable).start();

TtlRunnable.get()

根据步骤1提交任务(拷贝上下文),也就是在

TtlRunnable.get()

方法中,最后就是

new TtlRunnable()

privateTtlRunnable(@NonNullRunnable runnable,boolean releaseTtlValueReferenceAfterRun){//这就是去拷贝当前线程的上下文this.capturedRef =newAtomicReference<>(capture());this.runnable = runnable;this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;}

这里出现了一个工具类

Transmitter

,它主要的功能就是帮助我们拷贝,存放,重置

ThreadLocal

信息,这里

ThreadLocal

既可能是

ThreadLocal

也可能是

TransmittableThreadLocal

,接下来就先看看

Transmitter

Transmitter类

其中主要有几个方法需要关注,分别是

  1. 拷贝上下文capture()
  2. 存放上下文replay()
  3. 重置上下文restore()

实际的实现类是

Transmittee

,保存在

privatestaticfinalSet<Transmittee<Object,Object>> transmitteeSet =newCopyOnWriteArraySet<>();

总共有两个,一个处理

ThreadLocal

,一个处理

TransmittableThreadLocal


这里就以第二个的拷贝为例,详细代码可以去

TransmittableThreadLocal.class

中查看:

publicHashMap<TransmittableThreadLocal<Object>,Object>capture(){finalHashMap<TransmittableThreadLocal<Object>,Object> ttl2Value =newHashMap<>(holder.get().size());for(TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()){//这里需要注意下,确定copyValue()的拷贝方式
                            ttl2Value.put(threadLocal, threadLocal.copyValue());}return ttl2Value;}

这里的

copyValue()

方法需要注意下,目前是直接使用value对象,如果是引用对象,那么就会受外层的影响。如果想进行深拷贝,需要使用

SuppliedTransmittableThreadLocal

类。
通过

TransmittableThreadLocal.withInitialAndCopier()

方法,提供对应拷贝方法

privatestaticfinalclassSuppliedTransmittableThreadLocal<T>extendsTransmittableThreadLocal<T>{privatefinalSupplier<?extendsT> supplier;privatefinalTtlCopier<T> copierForChildValue;privatefinalTtlCopier<T> copierForCopy;

还有一个就是成员变量

holder

。这个

holder

中存放了所有

TransmittableThreadLocal

的引用,而拷贝其实就是将

TransmittableThreadLocal

的引用和当时其中的值拷贝(取决于拷贝的方式,对于引用类型要考虑是否能受外层影响)到

capturedRef

成员变量中,这样

TtlRunnable

就能在运行时获取到上下文了。

下图在总体流程上描述

new TtlRunnable()

的整个过程
在这里插入图片描述
到此拷贝已经完成,接下来就是使用前进行值设置。

TtlRunnable.run()

使用前也就是在任务运行前

//1. 获取快照,也就是Snapshot()finalObject captured = capturedRef.get();if(captured ==null|| releaseTtlValueReferenceAfterRun &&!capturedRef.compareAndSet(captured,null)){thrownewIllegalStateException("TTL value reference is released after run!");}//2. 将快照中的值设置到当前线程的上下文中(也就是TransmittableThreadLocal或者ThreadLocal)//3. 返回backup,就是在设置之前,当前线程的快照信息finalObject backup =replay(captured);try{
            runnable.run();}finally{//4.将设置的当前线程快照信息给重新设置回去restore(backup);}

第一步也就是上面我们刚聊过的,就不过多赘述了。
第二步也就是设置上下文
第三步设置backup

接下来我们来看下

replay()

方法的源码,以及第三步

backup

和第四步

restore()

方法的必要性。

replay()
publicHashMap<TransmittableThreadLocal<Object>,Object>replay(@NonNullHashMap<TransmittableThreadLocal<Object>,Object> captured){finalHashMap<TransmittableThreadLocal<Object>,Object> backup =newHashMap<>(holder.get().size());for(finalIterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext();){TransmittableThreadLocal<Object> threadLocal = iterator.next();//1.获取当前线程,上线文快照
                            backup.put(threadLocal, threadLocal.get());//2.如果当前线程有快照里面不存在的上下文,那么先清除掉if(!captured.containsKey(threadLocal)){
                                iterator.remove();
                                threadLocal.superRemove();}}//3.将创建TtlRunnable时保存的快照设置到当前线程的上下文中setTtlValuesTo(captured);//4.保留的一个hook用于自定义doExecuteCallback(true);//5.返回保存的快照return backup;}

#mermaid-svg-IGeslXkZponOjpXF {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-IGeslXkZponOjpXF .error-icon{fill:#552222;}#mermaid-svg-IGeslXkZponOjpXF .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IGeslXkZponOjpXF .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-IGeslXkZponOjpXF .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IGeslXkZponOjpXF .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IGeslXkZponOjpXF .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IGeslXkZponOjpXF .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IGeslXkZponOjpXF .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IGeslXkZponOjpXF .marker.cross{stroke:#333333;}#mermaid-svg-IGeslXkZponOjpXF svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IGeslXkZponOjpXF .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-IGeslXkZponOjpXF text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-IGeslXkZponOjpXF .actor-line{stroke:grey;}#mermaid-svg-IGeslXkZponOjpXF .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-IGeslXkZponOjpXF .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-IGeslXkZponOjpXF #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-IGeslXkZponOjpXF .sequenceNumber{fill:white;}#mermaid-svg-IGeslXkZponOjpXF #sequencenumber{fill:#333;}#mermaid-svg-IGeslXkZponOjpXF #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-IGeslXkZponOjpXF .messageText{fill:#333;stroke:#333;}#mermaid-svg-IGeslXkZponOjpXF .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-IGeslXkZponOjpXF .labelText,#mermaid-svg-IGeslXkZponOjpXF .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-IGeslXkZponOjpXF .loopText,#mermaid-svg-IGeslXkZponOjpXF .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-IGeslXkZponOjpXF .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-IGeslXkZponOjpXF .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-IGeslXkZponOjpXF .noteText,#mermaid-svg-IGeslXkZponOjpXF .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-IGeslXkZponOjpXF .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-IGeslXkZponOjpXF .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-IGeslXkZponOjpXF .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-IGeslXkZponOjpXF .actorPopupMenu{position:absolute;}#mermaid-svg-IGeslXkZponOjpXF .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-IGeslXkZponOjpXF .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-IGeslXkZponOjpXF .actor-man circle,#mermaid-svg-IGeslXkZponOjpXF line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-IGeslXkZponOjpXF :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}
当前线程

   线程池 
 
   执行线程 
 
上下文为1 

提交任务进行拷贝 

拷贝上下文为1 

等待线程执行 

设置自己上下文2 

执行任务 

backup保存当前上下文2 

replay设置拷贝的上下文1 

执行任务使用Ttl 1 

restore重置上下文2 

执行完毕 

  当前线程 

  线程池 

  执行线程 

到这里

TransmittableThreadLocal

大体执行流程就分析完毕,还涉及到的一些方法可以深入源码中去查看下。

觉得不错的同学可以关注下我哟,不定期更新工作中遇到的问题以及解决方案(* ̄︶ ̄)

标签: java

本文转载自: https://blog.csdn.net/qq_18300037/article/details/128776124
版权归原作者 唐芬奇 所有, 如有侵权,请联系我们删除。

“TransmittableThreadLocal详解”的评论:

还没有评论