iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > JAVA >TransmittableThreadLocal详解
  • 799
分享到

TransmittableThreadLocal详解

java 2023-09-09 11:09:51 799人浏览 薄情痞子
摘要

文章目录 介绍需要解决的问题源码TtlRunnable.get()Transmitter类TtlRunnable.run()replay() 介绍 Transmittab

文章目录

介绍

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

需要解决的问题

通过上一篇文章我们了解到ThreadLocalInheritable所存在的局限性,针对这种局限性TTL提出并实现了一种解决方案。

如果对于TTL和上面所说的局限性,没有清晰概念的同学可以看下ThreadLocal与InheritableThreadLocal的实现原理和上面TTLGitHub上面的介绍

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

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

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

aid-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. 什么时候设置上下文?
    由于前面我们知道,在任务提交和执行存在一定的时间差,那么设置上下文的时候,就不能是创建的时候,只能是在执行之前(如果在创建的时候,还需要考虑,中途如果没轮到该任务执行就设置了上下文,线程如果还有其它的流程需要执行,就会导致上下文丢失问题)

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

当前线程 线程池 新线程(其它线程) 1.提交任务Runable(拷贝当前上下文) 2. 等待执行 3. 执行任务 4. 将当前线程上下文保存一份(看问题3) 5. 将拷贝的上下文设置到TTL中 6. run(),执行任务通过TTL获取上下文 7. 将4中保存重新设置到TTL中 当前线程 线程池 新线程(其它线程)

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

源码

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

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

最简单的使用方式:

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

TtlRunnable.get()

根据步骤1提交任务(拷贝上下文),也就是在TtlRunnable.get()方法中,最后就是new TtlRunnable()

    private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {    //这就是去拷贝当前线程的上下文        this.capturedRef = new AtomicReference<>(capture());        this.runnable = runnable;        this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;    }

这里出现了一个工具Transmitter,它主要的功能就是帮助我们拷贝,存放,重置ThreadLocal信息,这里ThreadLocal既可能是ThreadLocal也可能是TransmittableThreadLocal,接下来就先看看Transmitter

Transmitter类

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

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

实际的实现类是Transmittee,保存在

private static final Set<Transmittee<Object, Object>> transmitteeSet = new CopyOnWriteArraySet<>();

总共有两个,一个处理ThreadLocal,一个处理TransmittableThreadLocal
这里就以第二个的拷贝为例,详细代码可以去TransmittableThreadLocal.class中查看:

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

这里的copyValue()方法需要注意下,目前是直接使用value对象,如果是引用对象,那么就会受外层的影响。如果想进行深拷贝,需要使用SuppliedTransmittableThreadLocal类。
通过TransmittableThreadLocal.withInitialAndCopier()方法,提供对应拷贝方法

    private static final class SuppliedTransmittableThreadLocal<T> extends TransmittableThreadLocal<T> {        private final Supplier<? extends T> supplier;        private final TtlCopier<T> copierForChildValue;        private final TtlCopier<T> copierForCopy;

还有一个就是成员变量holder。这个holder中存放了所有TransmittableThreadLocal的引用,而拷贝其实就是将TransmittableThreadLocal的引用和当时其中的值拷贝(取决于拷贝的方式,对于引用类型要考虑是否能受外层影响)到capturedRef成员变量中,这样TtlRunnable就能在运行时获取到上下文了。

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

TtlRunnable.run()

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

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

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

接下来我们来看下replay()方法的源码,以及第三步backup和第四步restore()方法的必要性。

replay()

                    public HashMap<TransmittableThreadLocal<Object>, Object> replay(@NonNull HashMap<TransmittableThreadLocal<Object>, Object> captured) {                        final HashMap<TransmittableThreadLocal<Object>, Object> backup = new HashMap<>(holder.get().size());                        for (final Iterator<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;                    }

对于1,2步骤,主要是把执行时刻的快照保存下来,等执行完后在设置会去,如果有点迷糊可以看下【问题三
backup解决的场景,提交执行的线程有自己的上下文(场景比较少,但是情况确实存在)

当前线程 线程池 执行线程 上下文为1 提交任务进行拷贝 拷贝上下文为1 等待线程执行 设置自己上下文2 执行任务 backup保存当前上下文2 replay设置拷贝的上下文1 执行任务使用Ttl 1 restore重置上下文2 执行完毕 当前线程 线程池 执行线程

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

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

来源地址:https://blog.csdn.net/qq_18300037/article/details/128776124

--结束END--

本文标题: TransmittableThreadLocal详解

本文链接: http://www.lsjlt.com/news/401472.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • TransmittableThreadLocal详解
    文章目录 介绍需要解决的问题源码TtlRunnable.get()Transmitter类TtlRunnable.run()replay() 介绍 Transmittab...
    99+
    2023-09-09
    java
  • EventBus详解 (详解 + 原理)
    一、EventBus的使用介绍 EventBus简介 EventBus是一个开源库,由GreenRobot开发而来,是用于Android开发的 “事件发布—订阅总线”, 用来进行模块间通信、解藕。它可以使用很少的代码,来实现多组件之间...
    99+
    2023-08-31
    android
  • @PreAuthorize注解详解
    @PreAuthorize注解会在方法执行前进行权限验证,支持Spring EL表达式,它是基于方法注解的权限解决方案。只有当@EnableGlobalMethodSecurity(prePostEnabled=true)的时候,@PreA...
    99+
    2023-09-14
    spring java mybatis
  • @TableField注解详解
    @TableField(value = "email")//指定数据库表中字段名 如果数据库和实体类的字段名不一致,可以使用@TableField注解指定数据库表中字段名。  2、@TableField(exist = "false")/...
    99+
    2023-09-02
    数据库 sql java
  • Spring注解详解
    概述 注释配置相对于 XML 配置具有很多的优势:它可以充分利用 Java 的反射机制获取类结构信息,这些信息可以有效减少配置的工作。如使用 JPA 注释配置 ORM 映射时,我们就不需要指定 PO 的属性名、类型等信息,如果关系表字段和...
    99+
    2023-06-03
  • Java注解详解
    目录 一、发现注解二、注解是什么1. 注解的本质2. 注解是针对Java编译器的说明 三、为什么要使用注解四、Java中常用的注解4.1 基础注解(spring-context、spring-we...
    99+
    2023-08-22
    java spring 软件测试
  • AJAX 详解
    文章目录 前言一 Ajax技术与原理1.1 Ajax简介1.2 Ajax所包含的技术1.3 Ajax的工作原理1.4 XMLHttpRequest 对象的三个常用的属性1. onreadyst...
    99+
    2023-09-24
    ajax javascript 服务器 安全
  • json.stringify()详解
    `JSON.stringify()` 是一个 JSON 对象的方法,用于将一个 JavaScript 值转换为一个 JSON 字符串...
    99+
    2023-09-13
    详解
  • groupby详解
    一、groupby详解 上周五同组同事有一个关于sql的bug,经leader看完后大体定位在group by语句用法错误。然后leader把我叫上一起,询问我们关于group by的用法。而我当时脑...
    99+
    2023-09-14
    java mysql sql
  • DNS详解
    1 背景 DNS 是 Domain Name System 的缩写,即域名系统。 DNS可以理解是将域名(如:www.baidu.com)和IP地址进行相互映射的一个分布式数据库。人直接去记忆IP地址数串是相当困难的(像是电话号码,记忆常用...
    99+
    2023-10-10
    网络 服务器 DNS
  • classpath详解
    可通过对 JDK 工具使用 -classpath 选项(首选方法)或设置 CLASSPATH 环境变量来设置类路径。 [@more@]C:> jdkTool -classpath path2;path3... C:> set CLASSPA...
    99+
    2023-06-03
  • jpa详解
    走进JPA 文章目录 前言一、JPA是什么?二、使用步骤1.配置数据源2.导入依赖3.创建实体类并通过注解方式让数据库知道我们的表长什么样4.启动测试类完成表创建(随便一个空的测试类就行)5.如何访问我们的表,也就是如何对表进...
    99+
    2023-08-17
    java 数据库 spring
  • 详解@Override注解
    目录 1.是什么 2.为什么用 3.举例说明 1)示例一 2)示例二 3)示例三 1.是什么 @Override注解是伪代码,用于表示被标注的方法是一个重写方法。 @Override注解,只能用于标记方法,并且它只在编译期生效,不会保留...
    99+
    2023-09-24
    java
  • MySQL详解
    MySQL 1、初识MySQL javaEE:企业级java开发 Web 前端(页面:展示,数据!) 后台(连接点:连接数据库JDBC,连接前端(控制视图跳转和给前端传递数据)) 数据库(存取数据,主要是存数据) 只会写代码,只是一个基...
    99+
    2023-08-18
    经验分享 mysql 数据库
  • 【ThreadLocal详解】
    ThreadLocal   ThreadLocal是一个用于实现线程数据隔离的一个类,每个线程访问时,通过Get、Set方法都会产生一个属于该线程的局部变量副本,当线程结束时,ThreadLocal及变量随着线程一起被回收。 Threa...
    99+
    2023-08-19
    java jvm 面试
  • SpringBoot详解
    前言 所有的技术框架的发展似乎都遵循了一条主线规律: 从一个复杂应用场景衍生一种规范框架, 人们只需要进行各种配置而不需要自己去实现它, 这时候强大的配置功能成了优点;发展到一定程度之后, 人们根据实际生产应用情况, 选取其中实用功能...
    99+
    2023-08-19
    spring boot java
  • AtomicInteger详解
    提示:想快速解决问题,建议直接点标题中的AtomicInteger对原子操作的常用方法。 文章目录 一、AtomicInteger定义二、AtomicInteger使用场景三、AtomicInteger作用四、AtomicIn...
    99+
    2023-08-20
    java
  • random.nextint()详解
    random.nextInt() 是 Java 中 Random 类的一个方法,用于生成一个随机的整数。语法:random.next...
    99+
    2023-08-30
    详解
  • composer详解
    一.composer简介 什么是Composer Composer 是 PHP 的一个依赖管理工具,它涉及 "packages" 和 "libraries",简单的说就是我们的项目通常会使用其它代码工具库,这时仅仅是在项目中申明依...
    99+
    2023-08-31
    composer php 开发语言 Powered by 金山文档
  • zookeeper详解
    一 zookeeper介绍         首先需要了解zookeeper是什么,zookeeper是一个分布式协调服务。所谓分布式协调主要是来解决分布式系统中多个进程之间的同步限制,防止出现脏读,例如我们常说的分布式锁。         ...
    99+
    2023-09-12
    java
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作