首页 > 其他分享 >异步线程变量传递必知必会---InheritableThreadLocal及底层原理分析

异步线程变量传递必知必会---InheritableThreadLocal及底层原理分析

时间:2023-08-11 14:12:04浏览次数:51  
标签:ITL parent 必知 --- ThreadLocal 线程 throwable threadPoolTaskExecutor

InheritableThreadLocal简介

笑傲菌:多线程热知识(一):ThreadLocal简介及底层原理3 赞同 · 0 评论文章

上一篇文章我们聊到了ThreadLocal的作用机理,但是在文章的末尾,我提到了一个问题,ThreadLocal无法实现异步线程变量的传递。

什么意思呢?以下面的代码为例子:

@SneakyThrows
public Boolean testThreadLocal(String s){
    LOGGER.info("实际传入的值为: " + s);
    DemoContext.setContext(Integer.valueOf(s)); // DemoContext为相应的ThreadLocal对象
    CompletableFuture<Throwable> subThread = CompletableFuture.supplyAsync(()->{
        try{
            //打印子线程的值
            LOGGER.info(String.format("子线程id=%s,contextStr为:%s",
                                      Thread.currentThread().getId(),DemoContext.getContext()));
        }catch (Throwable throwable){
            return throwable;
        }
        return null;
    });
    //打印主线程的值
    LOGGER.info(String.format("主线程id=%s,contextStr为:%s",
                              Thread.currentThread().getId(),DemoContext.getContext()));
    Throwable throwable = subThread.get();
    if (throwable!=null){
        throw throwable;
    }
    DemoContext.clearContext();
    return true;
}

原本我们期待的结果是,子线程中的值与主线程中的值保持一致,但是实际上,运行代码返回的结果是:

由此可见,ThreadLocal并没有按照所想的那样将相应的ThreadLocal的值传递到相应的异步线程上。

为了实现异步线程变量的传递,InheritableThreadLocal应运而生(以下简称为:ITL)。

我们将上述的代码稍作改动,将demoContext的类型转换成ITL之后再运行一次代码。可以看到结果如下:

 

ITL虽然传递了主线程的变量信息,但是在特定场景下也会出现问题。例如在上面的代码中,如果我们设置相应的线程池再来请求的话,就会出现问题。源码如下:

@SneakyThrows
public Boolean testThreadLocal(String s){
    ...

    CompletableFuture<Throwable> subThread = CompletableFuture.supplyAsync(()->{
        try{
            //打印子线程的值
            LOGGER.info(String.format("子线程id=%s,contextStr为:%s",
                                      Thread.currentThread().getId(),DemoContext.getContext()));
        }catch (Throwable throwable){
            return throwable;
        }
        return null;
    },demoExecutor); // 设置了线程池

    ...
}

@Bean(name = "demoExecutor")
public Executor demoExecutor() {
    ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
    threadPoolTaskExecutor.setTaskDecorator(new GatewayHeaderTaskDecorator());
    threadPoolTaskExecutor.setCorePoolSize(5);
    threadPoolTaskExecutor.setQueueCapacity(0);
    threadPoolTaskExecutor.setKeepAliveSeconds(3600);
    threadPoolTaskExecutor.setMaxPoolSize(1);
    threadPoolTaskExecutor.setThreadNamePrefix("demoExecutor-");
    threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
    threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
    threadPoolTaskExecutor.initialize();
    return threadPoolTaskExecutor;
}

代码运行起来的结果如下:

可以看到,多次请求后,线程间的变量出现了混乱传递,即实际传入的值,与子线程中拿到的值并不一样。这又是什么原因呢?

底层原理分析

要了解这个问题的原因,我们不得不了解下ITL的工作机制。

其实看起来,但是其实ITL的工作机制很简单,就是在子线程初始化的时候,将父线程的ITL给继承过来。具体来看Thread类中相应的init源码:

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        Thread parent = currentThread(); // 先找到他的爸爸

        this.daemon = parent.isDaemon(); //判断是否需要创建的是daemon线程
        this.priority = parent.getPriority(); // 保持跟他爸一样的优先级
        if (security == null || isCCLOverridden(parent.getClass())) // 获取相应的加载器
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        /*关键性代码*/
        // 在这里会判断当前的是否需要inheritThreadLocals
        //如果需要,那么会将当前创建这个线程的InheritableThreadLocals都获取过来,相当于获取了一份父类表的拷贝。
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        this.stackSize = stackSize;
        tid = nextThreadID(); // 设置ThreadID
    }

这种场景,在不使用线程池的情况是没有问题的。但是如果搭配上了线程池,就会存在问题。这里我们先简单介绍一下线程池的作用机理。

其中最关键的点在于,线程池会复用原有线程,致使部分线程不会经过Init初始化的过程,ITL的值也就没有办法得到更新。最终造成了错误的数据传递。

优劣势分析

优势:

1、通过在线程初始化的时候传递相应的ThreadLocal变量,解决了非线程池下的异步线程的变量传递问题。

劣势:

1、线程池复用线程和ITL底层机制无法兼容,导致了ITL无法结合线程池发挥作用。

总结:

在不依赖于线程池的场景下,ITL是一个很好的实现异步线程传递变量的工具。

然而,在使用线程池的情况下,由于线程不会进行频繁地初始化和销毁等工作,ITL的变量值无法得到更新,因而有可能存在数据错误传递的问题。

转自:https://zhuanlan.zhihu.com/p/469859090

标签:ITL,parent,必知,---,ThreadLocal,线程,throwable,threadPoolTaskExecutor
From: https://www.cnblogs.com/tiancai/p/17622825.html

相关文章

  • 无涯教程-Perl - lc函数
    描述此函数返回小写版本的EXPR,如果省略EXPR,则返回$_。语法以下是此函数的简单语法-lcEXPRlc返回值此函数返回小写版本的EXPR,如果省略EXPR,则返回$_。例以下是显示其基本用法的示例代码-#!/usr/bin/perl$orig_string="ThisisTestandCAPITAL";$changed_st......
  • 路径规划算法:基于食肉植物优化的机器人路径规划算法- 附matlab代码
    ✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。......
  • 路径规划算法:基于广义正态分布优化的机器人路径规划算法- 附matlab代码
    ✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。......
  • 路径规划算法:基于战争策略优化的机器人路径规划算法- 附matlab代码
    ✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信。......
  • Android View绘制原理-RenderPipeline
    在上一篇关于帧绘制的原理中,做好了EGLSuface切换,同步好了UI的更新,为需要进行GPU绘制的RenderNode创好了SKSurface,最后通过ANativeWindow为下一帧调用了dequeueBuffer。所有的资源和数据都准备好了,从而可以进行绘制,这个任务将由RenderPipeline来完成。我们先不考虑Fence的逻辑,直接接......
  • 数据库数据恢复-Oracle ASM数据恢复案例
    数据库数据恢复环境:Oracle数据库ASM磁盘组有4块成员盘。数据库故障&分析:Oracle数据库ASM磁盘组掉线,ASM实例无法挂载,用户联系我们要求恢复oracle数据库。数据库数据恢复工程师拿到磁盘后,先将所有磁盘以只读方式进行扇区级别的镜像备份,后续的数据分析和数据恢复都基于镜像文件进......
  • max-element的min-element的基本用法
    转载自https://blog.csdn.net/qq_37978559/article/details/109782755?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522169173063816800197041324%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=169173063816800197041324&biz......
  • NextJS - 使用 next-auth 配置 JWT token
    Nextjs中有很多身份验证选项,例如Supabase、Firebase、Userbase等等。我们将重点关注NextAuth.js以及通过凭证提供程序在现有Django后端和Next.js之间实现JWT会话的打字稿。我们将尽力专注于我们的用例以节省时间,因此我们将省略所有未使用的选项和功能。为什么选择N......
  • FTData063468_000001升级脚本出错,错误信息:SQL 脚本: 18.000.000.0048 DATA_DSTR_EAP_M
    一、问题:cjt15.0版本升级到18.0提示SQL脚本:18.000.000.0048DATA_DSTR_EAP_Mix_NL-11001出错:已在列上绑定了DEFAULT023-08-1019:46:39开始升级....2023-08-1019:46:39正在校验系统信息,请稍候...2023-08-1019:46:39[(000001)****]:开始升级2023-08-1019:46:39[(......
  • C#应用处理传入参数 - 开源研究系列文章
    今天介绍关于C#的程序传入参数的处理例子。      程序的传入参数应用比较普遍,特别是一个随操作系统启动的程序,需要设置程序启动的时候不显示主窗体,而是在后台运行,于是就有了传入参数问题,比如传入/h或者/min等等。所以此文就介绍一下关于程序传入参数的处理问题。......