ThreadLocal与InheritableThreadLocal及线程池的影响

2023-11-09

在web开发中使用了ThreadLocal本地线程存储拦截器解析的用户信息,方便在下文代码中调用,但是在springboot中使用@Async开启异步操作时,就会造成,子线程无法拿到父本地线程数据。拿到一些脏数据。

1.InheritableThreadLocal

  • 在这个问题上,java为我们提供了InheritableThreadLocal类,该类是继承了ThreadLocal,它具有与普通ThreadLocal相同的功能,但还具有一个额外的特性,即可以在子线程中继承父线程的变量值。
  • 普通的ThreadLocal变量只是在每个线程内部创建副本,而InheritableThreadLocal可以将父线程的变量值传递给子线程。这对于需要跨线程传递上下文信息或者共享线程间状态的场景非常有用。
private static ThreadLocal<String> fatherInheritableThread = new InheritableThreadLocal<>();

探讨

使用InheritableThreadLocal类可以实现父子线程的共享,通过源码发现,我们当前线程A里正在创建子线程A-1,A线程去判断自己的inheritableThreadLocals是否有值,有值返回给当前正在创建的A-1的inheritableThreadLocals属性。
注意,这里代码有点绕,一看咋是自己赋值给自己,但仔细来看, Thread parent = currentThread();parent是当前正在new Thread()的线程(正在创造A-1),是A-1线程拿到了当前正在运行自己的A线程的inheritableThreadLocals然后this.inheritableThreadLocals = inheritableThreadLocals赋值给了自己。this当然就是A-1了。A线程是父线程其数据当然是我们主动set的了。到此,一个新的子线程A-1诞生了。
在这里插入图片描述
子线程A-1创建完后,InheritableThreadLocal.get()获取数据,get方法没有被重写,其位于父类ThreadLocal里面。
在这里插入图片描述
InheritableThreadLocal重写getMap,使之在get方法时,获取this.InheritableThreadLocals的数据,
在这里插入图片描述
在构造ThreadLocalMap时,调用了InheritableThreadLocal重写的childValue,直接返回父线程值。
在这里插入图片描述
容易混淆的是,在这里,我们操作的是inheritableThreadLocals属性,可以理解为对于一个线程,threadLocals与inheritableThreadLocals是平级关系。

问题

也就是说,只有当子线程被new的时候,父子线程才会同步数据,对于使用线程池模式线程复用的情况下,就会出现多线程问题,即A线程的value=5,A线程任务结束后,执行B任务,A线程至始至终没有被销毁,所以B任务始终无法获取父线程最新的数据,从而导致污染。InheritableThreadLocal并没有改变Thread类的行为,而是在Thread类中新增了一个成员变量来存储线程的变量副本。它利用了Java的继承机制,在子线程创建时复制父线程的变量副本,实现了父线程变量传递给子线程的功能。

当创建一个新的子线程时,Java会调用Thread类的init() 方法来初始化该线程。在init()方法中,会通过inheritableThreadLocals的createInheritedMap(ThreadLocalMap parentMap)方法来创建一个新的InheritableThreadLocalMap对象,并将父线程的inheritableThreadLocals中的值复制到新的InheritableThreadLocalMap对象中。如果线程被复用,则不会调用init方法从而没有以上效应。

线程池线程复用实例:

package com.liubingzhe.smartFile;

import org.springframework.core.task.TaskDecorator;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;


public class ThreadTest {

    private static ThreadLocal<String> fatherThread = new ThreadLocal<>();
    private static ThreadLocal<String> fatherInheritableThread = new InheritableThreadLocal<>();

    static class FirstTask implements Runnable {
        @Override
        public void run() {
            System.out.println("[FirstTask] print ThreadLocal: " + fatherThread.get());
            System.out.println("[FirstTask] print InheritableThreadLocal:" + fatherInheritableThread.get());
        }
    }

    static class SecondTask implements Runnable {
        @Override
        public void run() {
            System.out.println("[SecondTask] print ThreadLocal: " + fatherThread.get());
            System.out.println("[SecondTask] print InheritableThreadLocal:" + fatherInheritableThread.get());
        }
    }


    public static void main(String[] args) throws InterruptedException {
        ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
        // 限制线程数为1
        threadPoolTaskExecutor.setMaxPoolSize(1);
        threadPoolTaskExecutor.setCorePoolSize(1);
        threadPoolTaskExecutor.setQueueCapacity(1);
        threadPoolTaskExecutor.setThreadNamePrefix("async-");
        threadPoolTaskExecutor.initialize();
        String printText = "this is father thread print";
        fatherInheritableThread.set(printText);
        fatherThread.set(printText);
        System.out.println("父线程初始数据");
        System.out.println("fatherThread print: " + fatherThread.get());
        System.out.println("fatherInheritableThread print: " + fatherInheritableThread.get());
        System.out.println("子线程1开始执行");
        threadPoolTaskExecutor.execute(new FirstTask());
        printText = "change text";
        fatherInheritableThread.set(printText);
        Thread.sleep(2000);
        System.out.println("子线程2开始执行");
        threadPoolTaskExecutor.execute(new SecondTask());

    }
}

我们限制线程池仅允许一个线程使之复用,并执行这两个异步,中途我们修改printText的值,观察结果:

父线程初始数据
fatherThread print: this is father thread print
fatherInheritableThread print: this is father thread print
子线程1开始执行
[FirstTask] print ThreadLocal: null
[FirstTask] print InheritableThreadLocal:this is father thread print
子线程2开始执行
[SecondTask] print ThreadLocal: null
[SecondTask] print InheritableThreadLocal:this is father thread print

可以发现子线程2并没有发生改变。通过在子线程中增加 System.out.println(Thread.currentThread().getId());可以进一步确定是一个线程在运行,当然也只能是一个。

怎么解决?

  1. TransmittableThreadLocal可以解决
  2. 使用线程池装饰器threadPoolTaskExecutor.setTaskDecorator(new myDecorator());
  • 在线程运行前,获取当前父线程上下文的值
  • 执行前给当前任务创建一个新的本地线程,并将父线程上下文数据赋值给当前新的本地线程,然后赋值给父线程(本质创建了一个父线程副本)
    static class myDecorator implements TaskDecorator{

        @Override
        public Runnable decorate(Runnable runnable) {
            String fatherValue = fatherInheritableThread.get();
            return ()->{
                InheritableThreadLocal<String> objectThreadLocal = new InheritableThreadLocal<>();
                try{
                    objectThreadLocal.set(fatherValue);
                    fatherThread = objectThreadLocal;
                    runnable.run();
                }finally {
                    objectThreadLocal.remove();
                }

            };
        }
    }

在一个线程情况下的打印结果,数据发生改变

父线程初始数据
fatherThread print: this is father thread print
fatherInheritableThread print: this is father thread print
子线程1开始执行
12
[FirstTask] print ThreadLocal: this is father thread print
[FirstTask] print InheritableThreadLocal:this is father thread print
子线程2开始执行
12
[SecondTask] print ThreadLocal: change text
[SecondTask] print InheritableThreadLocal:this is father thread print

第二种方法是我在开发过程中临时想到了一个解决方法,可能存在一些问题,但是后来通过来了解TransmittableThreadLocal类发现,基本思路一致,都是创建了线程副本,但是其是通过在切换线程时,还原了线程状态。

TransmittableThreadLocal是一个第三方库,它通过字节码增强和拦截线程切换的方式实现了在线程之间传递上下文的功能。它是基于InheritableThreadLocal的变体,并且专门用于解决在线程池等场景下的线程传递问题。

下面是TransmittableThreadLocal的基本原理:

字节码增强:TransmittableThreadLocal使用字节码增强技术,在运行时修改Java字节码。在线程类(Thread)和线程池类(ThreadPoolExecutor)的相关方法中,插入了额外的代码。

线程切换的拦截:TransmittableThreadLocal使用ThreadLocal类的特性,通过继承ThreadLocal并重写相关方法来实现对线程切换的拦截。具体是重写ThreadLocalbeforeExecute()afterExecute()remove()方法,这些方法会在线程池中执行任务前后被调用,以及从线程池中移除线程时被调用。

状态保存与恢复:在线程切换之前,TransmittableThreadLocal会保存当前线程中所有TransmittableThreadLocal对象的状态或值。保存的方式是通过自动化的字节码增强操作,将相关状态存储到一个共享的数据结构中。

线程切换时的绑定与解绑操作:在线程切换之后,TransmittableThreadLocal会在新线程中恢复之前保存的值。它会根据线程的标识符(Thread ID)来获取对应的之前保存的状态。这样,新线程就能够访问到与之前线程相关的TransmittableThreadLocal对象的值。

总的来说,TransmittableThreadLocal通过使用字节码增强和拦截线程切换的方式,实现了在线程之间传递上下文的能力。通过保存和恢复线程中的TransmittableThreadLocal的状态,它确保在线程池等复杂场景下,正确地传递变量值,保持线程之间的隔离性
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ThreadLocal与InheritableThreadLocal及线程池的影响 的相关文章

  • JAVA 中的 Composer 相当于什么? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 我目前从 PHP 转向 java 有没有类似的工具composer https getcomposer org 在 PHP 中用于 JAV
  • Java,顺序流在哪个线程中执行?

    在阅读有关流的文档时 我遇到了以下句子 attempting to access mutable state from behavioral parameters presents you with a bad choice if you
  • java 中的梵文 i18n

    我正在尝试使用来自互联网的示例 ttf 文件在 java 中使用 i18n 进行梵文 印地文 我可以加载资源包条目 还可以加载 ttf 并设置字体 但它不会根据需要呈现 jlabel 它显示块代替字符 如果我在 Eclipse 中调试 我可
  • 如何准确判断 double 是否为整数? [复制]

    这个问题在这里已经有答案了 具体来说 在 Java 中 我如何确定double是一个整数 为了澄清 我想知道如何确定 double 实际上不包含任何分数或小数 我主要关心的是浮点数的性质 我想到的方法 以及我通过谷歌找到的方法 基本上遵循以
  • 如何将本机数据库运算符 (postgres ~) 与 JPA 标准生成器一起使用?

    我使用 JPA 2 0 标准构建以下查询 简化 select n from notif n where n message b la 我正在使用 postgresql 数据库 我真的需要 运算符 而不是像 我可以使用与 CriteriaBu
  • 绘制平滑曲线

    我想创建更平滑的曲线 而不仅仅是线角 这是我现在画的图 这是我的代码 case FREEHAND float pts float ptk ptk new float 2 imageMatrix invert inv if mCurrentS
  • 在拇指上方显示修改后的 JSlider 值

    有没有一种简单的方法可以在使用某些 外观和感觉 的同时更改 JSlider 上方标签中显示的值 为了清楚起见 我正在谈论这个值 具体来说 我想显示除以 1000 的值而不是值本身 我知道如果我显示它们 我可以为刻度设置标签 但用户将不得不猜
  • Java 中如何验证字符串的格式是否正确

    我目前正在用 Java 编写一个验证方法来检查字符串是否是要更改为日期的几种不同格式之一 我希望它接受的格式如下 MM DD YY M DD YY MM D YY 和 M D YY 我正在测试第一种格式 每次它都告诉我它无效 即使我输入了有
  • 如何在Netbeans中设置JList的ListModel?

    我在 Netbeans IDE 的帮助下设计了一个 Swing GUI 该 GUI 包含一个 JList 默认情况下 它使用 QAbstractListModel 将其作为 JList 构造函数中的参数传递以创建该 JList 我想在 Ne
  • 在Java中如何将字节数组转换为十六进制?

    我有一个字节数组 我希望该数组的每个字节字符串转换为其相应的十六进制值 Java中有没有将字节数组转换为十六进制的函数 byte bytes 1 0 1 2 3 StringBuilder sb new StringBuilder for
  • java中如何重新初始化int数组

    class PassingRefByVal static void Change int pArray pArray 0 888 This change affects the original element pArray new int
  • jDBI中如何进行内查询?

    我怎样才能在 jDBI 中执行这样的事情 SqlQuery select id from foo where name in
  • 如何使用 Java 在 selenium webdriver 中打开新选项卡或如何使用使用 selenium webdriver 的操作类在 selenium 中按 ctrl +T [重复]

    这个问题在这里已经有答案了 如何使用 Java 在 Selenium Webdriver 中按 CTRL T 或者 如何使用 Java 在 selenium webdriver 中打开新选项卡 简单步骤 1 打开google com 不必触
  • JPA Web 应用程序管理策略

    我们目前正在开发一个 J2EE Web 应用程序 使用 JPA 作为我们的数据访问层 我们目前正在研究几种不同的策略来在我们的应用程序中利用缓存 Create an EntityManager per request 在请求范围内获取缓存
  • 从字节数组设置 img src

    我需要设置img src我在对象中拥有的字节数组的属性 img
  • 如何使用eclipse调试JSP tomcat服务?

    我想使用 Eclipse IDE 调试器来调试单独运行的 JSP Struts Tomcat Hibernate 应用程序堆栈 如何设置 java JVM 和 eclipse 以便设置断点 监视变量值并查看当前正在执行的代码 我刚刚用谷歌搜
  • 检查按钮是否可用?如果没有,请等待 5 秒钟,然后再次检查?

    基本上我想看看此刻是否可以单击按钮 如果没有我想再试一次 所以我需要某种 goto 函数来返回到代码的前一行 尽管我怀疑我写得非常糟糕 但它本来可以做得更容易 try driver findElement By xpath button i
  • Firebase:用户注册后如何进行电话号码验证?

    所以我知道我可以使用电子邮件验证或电话号码验证 但我想做的是在用户注册或登录后进行电话号码验证 如何连接这两种身份验证方法 最后 Firebase中是否有一个函数可以检查用户是否通过电话号码验证 谢谢 即使用户已通过身份验证 您仍然可以使用
  • Java 中序列化的目的是什么?

    我读过很多关于序列化的文章 以及它如何如此美好和伟大 但没有一个论点足够令人信服 我想知道是否有人能真正告诉我通过序列化一个类我们真正可以实现什么 让我们先定义序列化 然后我们才能讨论它为什么如此有用 序列化只是将现有对象转换为字节数组 该
  • 我怎样才能限定我不“拥有”的自动装配设置器

    要点是 Spring Batch v2 测试框架具有JobLauncherTestUtils setJob与 Autowired注解 我们的测试套件有多个Job类提供者 由于这个类不是我可以修改的东西 我不确定如何限定它自动连接的作业 每个

随机推荐

  • 【SpringBoot新手篇】SpringBoot优雅文件上传方式

    SpringBoot文件上传 Pom yml controller UploadController FileController 文件上传 多文件上传 文件下载 页面 BUG 封装版 工具类 StringUtil MimeTypeUtil
  • JAVA&狂神学习笔记_9.类型转换

    由于JAVA是强类型语言 所以在不同类型进行混合运算的时候 需要用到类型转换 byte short char int long float double 这个图表内的7个类型 从左至右优先级递增 在类型转换方面由涉及到了强制类型转换以及自动
  • 虚拟化技术 - CPU虚拟化

    这里写自定义目录标题 虚拟化技术 cpu虚拟化 kvm for x86 虚拟化技术 cpu虚拟化 物理机器是由CPU 内存和I O 设备等一组资源构成的实体 虚拟机也一样 由虚拟CPU 虚拟内存和虚拟IO设备组成 VMM VM Monito
  • 深度学习实战10-数学公式识别-将图片转换为Latex(img2Latex)

    大家好 我是微学AI 今天给大家带来一个关于数学公式识别的实战案例 解决大家在写论文中遇到很多latex输入的问题 而且可以无限次识别哦 因为是代码实现 不用调用外部API 以前我们知道一个latex识别网页 latex识别网页神器 htt
  • VS2015 python配置文件出错

    VS2015 python配置文件出错 昨天安装了anacond2 再打开以前的python项目运行 发现了这个问题 原因是anaconda修改了默认的python2 7这个环境 一堆路径什么的 明明cmd anaconda prompt都
  • Git恢复之前版本的两种方法reset、revert(图文详解)【学习】

    一 问题描述 在利用github实现多人合作程序开发的过程中 我们有时会出现错误提交的情况 此时我们希望能撤销提交操作 让程序回到提交前的样子 本文总结了两种解决方法 回退 reset 反做 revert 二 背景知识 git的版本管理 及
  • 关于C++中switch- case的问题

    如果switch语句在C 中这样写 case 1 printf 计算概论 double book1 28 9 total book1 break case 2 printf 数据结构与算法 double book2 32 7 total b
  • 基于深度学习方法与张量方法的图像去噪相关研究

    目录 1 研究现状 1 1 基于张量分解的高光谱图像去噪 1 2 基于深度学习的图像去噪算法 1 3 基于深度学习的高光谱去噪 1 4 小结 2 基于深度学习的图像去噪算法 2 1 深度神经网络基本知识 2 2 基于深度学习的图像去噪网络
  • matlab巴特沃斯滤波器用法

    基于matlab 的数字滤波器 clear clc fs 22050 wp 0 1 pi 通带截止频率 ws 0 4 pi 阻带截止频率 Rp 3 通带衰减率 Rs 75 阻带衰减率 Fs 22050 Ts 1 Fs wp1 2 Ts ta
  • 查看jar包工具——JByteMod学习及分享

    Aspose于2002年3月在澳大利亚悉尼创建 公司网站于2002年10月对外发布 Aspose 一直致力于成为全球最大的 Net 组件提供商 为全球 NET 程序员提供最丰富的选择 数十个国家的数千机构选择了Aspose的产品 这包括微软
  • 前端开发者需要去了解的一些Node.js知识以及应用场景

    注意 后续技术分享 第一时间更新 以及更多更及时的技术资讯和学习技术资料 将在公众号CTO Plus发布 请关注公众号 CTO Plus Node js系列文章推荐阅读 JavaScript匿名函数的定义 特性 作用和使用场景详解 Node
  • Ubuntu下安装mysql笔记

    1 首先更新本地存储库索引 执行sudo apt update 2 执行安装命令 sudo apt install mysql server y 遇到下面的报错 E Could not get lock var lib dpkg lock
  • flutter 自己发消息,列表跳到最底部,收到消息,如果不在底部就显示“有未读消息”,点击跳到最底部

    先判断该消息是否时自己发的 如果是自己发的 列表就跳到底部 如果不是自己发的消息 就判断是否在底部 如果不在底部就显示 有未读消息 如果在底部就不用显示 有未读消息 点击 有未读消息 跳转到列表底部 因为列表反转了 所以底部是0 顶部是列表
  • 常见的#pragma预处理命令

    pragma comment 将一个注释记录放置到对象文件或可执行文件中 pragma pack 用来改变编译器的字节对齐方式 pragma code seg 它能够设置程序中的函数在obj文件中所在的代码段 如果未指定参数 函数将放置在默
  • VLP-16 velodyne + kinect dk 复现 LeGO-LOAM

    参考 使用自己的激光雷达 数据集运行lego loam 修改代码教程 和道一文字 的博客 CSDN博客 LeGO LOAM 编译安装与运行 Yeah2333的博客 CSDN博客 lego loam运行 一 配置VLP16 sudo apt
  • Inkscape插入LaTeX公式

    Inkscape插入LaTeX公式 Inkscape软件自身没有插入公式的功能 在一些需要公式配合的图片 Inkscape无法正常制图 为了解决该问题 本文采用Inkscape中安装TexText扩展的方法 使得Inkscape在制图过程中
  • 在阿里云的生产环境下:nginx同一域名下配置多个静态页面

    背景说明 这两天公司前端开发工程师提出要求 在公司的主业务域名中加一个静态页面进去 在这里我就不透露公司的域名是什么 我们把域名估且为www ganbing com 这种需求很多公司是经常有的 写一个重定向啊 加个静态页面啊 实现跨域访问啊
  • java的值传递

    java中只有值传递 1 对于基本数据类型 改变形参的值不会影响实参的值 2 对于引用类型 改变形参的值会不会影响实参的值 这个我们得分情况 情况1 修改的是形参的指向的话就不改变原来实参的值 情况2 修改的是形参的值的话就会改变原来实参的
  • 使用three.js渲染第一个场景和物体

    一 效果图 二 渲染场景和物体的步骤 创建场景 Scene 在 three js 中创建场景通过调用 THREE Scene 方法 然后将其赋值给变量 var scene new THREE Scene 创建相机 Camera 在 thre
  • ThreadLocal与InheritableThreadLocal及线程池的影响

    在web开发中使用了ThreadLocal本地线程存储拦截器解析的用户信息 方便在下文代码中调用 但是在springboot中使用 Async开启异步操作时 就会造成 子线程无法拿到父本地线程数据 拿到一些脏数据 1 Inheritable