ThreadLocal 在上下文传值场景实践源码

目录
  • 开篇语
  • 1、回顾
  • 2、ThreadLocal实现
    • 2.1、定义ThreadLocal上下文工具类
  • 3、开启子线程
  • 4、线程池+ThreadLocal
  • 5、解决方案
  • 6、总结

开篇语

我们在 《打动面试官:线程池流程编排中的运用实战》一文中将流程引擎简单地完善了一下,本文在其基础上继续进行改造,建议同学可以先看看 GitHub 上的代码,或者看看之前的文章。

1、回顾

流程引擎编排的对象,我们称为组件(就是 SpringBean),之前我们给组件定义了通用的接口,组件实现时就实现这个接口,代码如下:

我们定义了 DomainAbilityBean 接口,入参和出参都是 FlowContent,FlowContent 我们称为上下文。

2、ThreadLocal 实现

上下文传参除了 FlowContent 实现外,ThreadLocal 也是可以实现的,我们来演示一下:

2.1、定义 ThreadLocal 上下文工具类

首先我们使用 ThreadLocal 定义了上下文工具类,并且定义了 put、get 方法,方便使用,代码如下:

public class ContextCache implements Serializable {
  private static final long serialVersionUID = 2136539028591849277L;
  // 使用 ThreadLocal 缓存上下文信息
  public static final ThreadLocal<Map<String,String>> CACHE = new ThreadLocal<>();
  /**
   * 放数据
   * @param sourceKey
   */
  public static final void putAttribute(String sourceKey,String value){
    Map<String,String> cacheMap = CACHE.get();
    if(null == cacheMap){
      cacheMap = new HashMap<>();
    }
    cacheMap.put(sourceKey,value);
    CACHE.set(cacheMap);
  }
  /**
   * 拿数据
   * @param sourceKey
   */
  public static final String getAttribute(String sourceKey){
    Map<String,String> cacheMap = CACHE.get();
    if(null == cacheMap){
      return null;
    }
    return cacheMap.get(sourceKey);
  }
}

如果你想往 ThreadLocal 放数据,调用 ContextCache.putAttribute 方法,如果想从 ThreadLocal 拿数据,调用 ContextCache.getAttribute 方法即可。

我们写了两个组件,一个组件放数据,一个组件拿数据,如下:

我们把两个 SpringBean 注册到流程注册中心中,让其按照先执行 BeanThree 再执行 BeanFive 的顺序进行执行,运行 DemoApplication 类的 main 方法进行执行,执行结果如下:

从打印的日志可以看到,在 Spring 容器管理的 SpringBean 中,ThreadLocal 也是可以储存中间缓存值的。

3、开启子线程

我们做一个实验,我们在 BeanFive 中开启子线程,然后再从 ThreadLocal 中拿值,看看能否拿到值,BeanFive 的代码修改成如下:

我们再来运行一下,打印的日志如下:

从打印的日志中,我们发现在子线程中从 ThreadLocal 取值时,并没有取得值,这个原因主要是我们之前说的,线程在创建的时候,并不会把父线程的 ThreadLocal 中的值拷贝给子线程的 ThreadLocal,解决方案就是把 ThreadLocal 修改成 InheritableThreadLocal,代码修改如下:

我们再次运行,结果如下:

从运行结果看,我们成功的在子线程中拿到值。

4、线程池 + ThreadLocal

如果是拿数据的 springBean 是丢给线程池执行的,我们能够成功的从 ThreadLocal 中拿到数据么?

首先我们在放数据的 springBean 中,把放的值修改成随机的,接着拿数据的 SpringBean 修改成异步执行,代码修改如下:

为了能快速看到效果,我们把线程池的 coreSize 和 maxSize 全部修改成 3,并让任务沉睡一段时间,这样三个线程肯定消费不完任务,大量任务都会到队列中去排队,我们修改一下测试脚本,如下:

我们期望的结果:

线程池中执行的 BeanFive 可以成功从 ThreadLocal 中拿到数据;能够从 ThreadLocal 拿到正确的数据,比如 BeanThree 刚放进 key1,value5,那么期望在 BeanFive 中根据 key1 能拿出 value5,而不是其它值。

我们运行一下,结果如下:

从结果中可以看到,并没有符合我们的预期,我们往 ThreadLocal 中 put 进很多值,但最后拿出来的值却很多都是 value379,都为最后 put 到 ThreadLocal 中的值。

这个原因主要是 ThreadLocal 存储的 HashMap 的引用都是同一个,main 主线程可以修改 HashMap 中的值,子线程从 ThreadLocal 中拿值时,也是从 HashMap 中拿值,从而导致不能把 put 的值通过 ThreadLocal 正确的传递给子线程。

为了证明是这个原因,我们在从 ThreadLocal 放、拿值的地方,把 HashMap 的内存地址都打印出来,改动代码如下:

我们再次运行测试代码,运行的结果如下:

从测试结果中可以看到,不管是主线程还是子线程和 ThreadLocal 进行交互时,HashMap 都是同一个,也就是说 ThreadLocal 中保存的 HashMap 是共享的,这就导致了线程安全的问题,子线程读取到的值就会混乱掉。

5、解决方案

针对这个问题,我们提出了一种解决方案,在把任务提交到线程池时,我们进行 HashMap 的拷贝,这样子线程的 HashMap 和 main 线程的 HashMap 就不同了,可以解决上面的问题。

我们提交任务时, 使用的是 Runnable,要实现 HashMap 的拷贝的话,我们需要把 Runnable 进行一层包装,包装的代码如下:

运行结果如下:

从运行结果中可以看出,线程池拿出来的 value 已经是正确的了。

6、总结

本文通过 ThreadLocal 来改造流程引擎中的上下文传递,希望能够加深大家对 ThreadLocal 的认识和使用技巧,有兴趣的同学可以把我们的代码下载下来,跑跑看。

以上就是ThreadLocal 在上下文传值场景下的实践的详细内容,更多关于ThreadLocal 上下文传值场景实践的资料请关注我们其它相关文章!

(0)

相关推荐

  • 基于ThreadLocal常用方法、使用场景及注意事项说明

    目录 1. ThreadLocal详解 2. ThreadLocal的使用场景 3.常用方法源码解析 3.1 initialValue方法 3.2 set(T value)方法 3.3 get方法 3.4 小结 3.4 ThreadLocalMap数据结构 4. ThreadLocal的副作用 4.1 ThreadLocal引起脏数据 4.2 ThreadLocal引起的内存泄漏 5. ThreadLocal内存泄漏解决方案及remove方法源码解析 1. ThreadLocal详解 JDK1.

  • 程序员最喜欢的ThreadLocal使用姿势

    目录 一.常见场景 二.进阶使用 三.使用漏洞 四.终阶使用 总结 一.常见场景 1.ThreadLocal作为线程上下文副本,那么一种最常见的使用方式就是用来方法隐式传参,通过提供的set()和get()两个public方法来实现在不同的方法中的参数传递.对于编程规范来说,方法定义的时候是对参数个数是有限制的,甚至在一些大厂,对方法参数个数是有明确规定的. 2.线程安全,每个线程维持自己的变量,以免紊乱,像常用的数据库的连接池的线程安全实现就使用了ThreadLocal. 二.进阶使用 以参数

  • Java ThreadLocal原理解析以及应用场景分析案例详解

    目录 ThreadLocal的定义 ThreadLocal的应用场景 ThreadLocal的demo TheadLocal的源码解析 ThreadLocal的set方法 ThreadLocal的get方法 ThreadLocalMap的结构 ThreadLocalMap的set方法 ThreadLocalMap的getEntry方法 ThreadLocal的内存泄露 如何避免内存泄露呢 应用实例 实际应用二 总结 ThreadLocal的定义 JDK对ThreadLocal的定义如下: The

  • Java 并发编程之ThreadLocal详解及实例

    Java 理解 ThreadLocal 摘要: ThreadLocal 又名线程局部变量,是 Java 中一种较为特殊的线程绑定机制,用于保证变量在不同线程间的隔离性,以方便每个线程处理自己的状态.进一步地,本文以ThreadLocal类的源码为切入点,深入分析了ThreadLocal类的作用原理,并给出应用场景和一般使用步骤. 一. 对 ThreadLocal 的理解 1). ThreadLocal 概述 ThreadLocal 又名 线程局部变量,是 Java 中一种较为特殊的 线程绑定机制

  • 有关ThreadLocal的面试题你真的懂了吗

    说明 面试官:讲讲你对ThreadLocal的一些理解. 那么我们该怎么回答呢????你也可以思考下,下面看看零度的思考: ThreadLocal用在什么地方? ThreadLocal一些细节! ThreadLocal的最佳实践! 思考 ThreadLocal用在什么地方? 讨论ThreadLocal用在什么地方前,我们先明确下,如果仅仅就一个线程,那么都不用谈ThreadLocal的,ThreadLocal是用在多线程的场景的!!! ThreadLocal归纳下来就2类用途: 保存线程上下文信

  • ThreadLocal 在上下文传值场景实践源码

    目录 开篇语 1.回顾 2.ThreadLocal实现 2.1.定义ThreadLocal上下文工具类 3.开启子线程 4.线程池+ThreadLocal 5.解决方案 6.总结 开篇语 我们在 <打动面试官:线程池流程编排中的运用实战>一文中将流程引擎简单地完善了一下,本文在其基础上继续进行改造,建议同学可以先看看 GitHub 上的代码,或者看看之前的文章. 1.回顾 流程引擎编排的对象,我们称为组件(就是 SpringBean),之前我们给组件定义了通用的接口,组件实现时就实现这个接口,

  • Vue高级组件之函数式组件的使用场景与源码分析

    目录 介绍 使用场景 源码分析 总结 介绍 Vue提供了一种可以让组件变为无状态.无实例的函数化组件.从原理上说,一般子组件都会经过实例化的过程,而单纯的函数组件并没有这个过程,它可以简单理解为一个中间层,只处理数据,不创建实例,也是由于这个行为,它的渲染开销会低很多.实际的应用场景是,当我们需要在多个组件中选择一个来代为渲染,或者在将children,props,data等数据传递给子组件前进行数据处理时,我们都可以用函数式组件来完成,它本质上也是对组件的一个外部包装. 使用场景 定义两个组件

  • 最通俗的白话讲解JDK源码中的ThreadLocal

    目录 引言 ThreadLocal是什么?它能干什么? ThreadLocal实现线程隔离的秘密 为什么ThreadLocal会出现OOM的问题? 内存泄漏演示 内存泄漏问题分析 父子线程的参数传递 总结 引言 其实网上有很多关于ThreadLocal的文章了,有不少文章也已经写的非常好了.但是很多同学反应还有一些部分没有讲解的十分清楚,还是有一定的疑惑没有想的十分清楚.因此本文主要结合常见的一些疑问.ThreadLocal源码.应用实例以注意事项来全面而深入地再详细讲解一遍ThreadLoca

  • JDK源码白话解读之ThreadLocal篇

    目录 引言 ThreadLocal是什么?它能干什么? ThreadLocal实现线程隔离的秘密 为什么ThreadLocal会出现OOM的问题? 内存泄漏演示 内存泄漏问题分析 父子线程的参数传递 总结 引言 因此本文主要结合常见的一些疑问.ThreadLocal源码.应用实例以注意事项来全面而深入地再详细讲解一遍ThreadLocal.希望大家看完本文后可以彻底掌握ThreadLocal. ThreadLocal是什么?它能干什么? 在阐述ThreadLocal之前,我们先来看下它的设计者是

  • Java源码解析ThreadLocal及使用场景

    ThreadLocal是在多线程环境下经常使用的一个类. 这个类并不是为了解决多线程间共享变量的问题.举个例子,在一个电商系统中,用一个Long型变量表示某个商品的库存量,多个线程需要访问库存量进行销售,并减去销售数量,以更新库存量.在这个场景中,是不能使用ThreadLocal类的. ThreadLocal适用的场景是,多个线程都需要使用一个变量,但这个变量的值不需要在各个线程间共享,各个线程都只使用自己的这个变量的值.这样的场景下,可以使用ThreadLocal.此外,我们使用ThreadL

  • 详解java实践SPI机制及浅析源码

    1.概念 正式步入今天的核心内容之前,溪源先给大家介绍一下关于SPI机制的相关概念,最后会提供实践源代码. SPI即Service Provider Interface,属于JDK内置的一种动态的服务提供发现机制,可以理解为运行时动态加载接口的实现类.更甚至,大家可以将SPI机制与设计模式中的策略模式建立联系. SPI机制: 从上图中理解SPI机制:标准化接口+策略模式+配置文件: SPI机制核心思想:系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编

  • Java Shutdown Hook场景使用及源码分析

    目录 背景 Shutdown Hook 介绍 关闭钩子被调用场景 注意事项 实践 Shutdown Hook 在 Spring 中的运用 背景 如果想在 Java 进程退出时,包括正常和异常退出,做一些额外处理工作,例如资源清理,对象销毁,内存数据持久化到磁盘,等待线程池处理完所有任务等等.特别是进程异常挂掉的情况,如果一些重要状态没及时保留下来,或线程池的任务没被处理完,有可能会造成严重问题.那该怎么办呢? Java 中的 Shutdown Hook 提供了比较好的方案.我们可以通过 Java

  • java编程ThreadLocal上下传递源码解析

    目录 引导语 1.用法演示 2.类结构 2.1.类泛型 2.2.关键属性 2.2.1.ThreadLocalMap 3.ThreadLocal是如何做到线程之间数据隔离的 4.set方法 5.get方法 6.扩容 7.总结 引导语 ThreadLocal 提供了一种方式,让在多线程环境下,每个线程都可以拥有自己独特的数据,并且可以在整个线程执行过程中,从上而下的传递. 1.用法演示 可能很多同学没有使用过 ThreadLocal,我们先来演示下 ThreadLocal 的用法,demo 如下:

  • Gradle 依赖切换源码实践示例详解

    目录 引言 1.一般的修改办法 2.通过 Gradle 脚本动态修改依赖 2.1 配置文件和工作流程抽象 2.2 为项目动态添加子工程 2.3 使用子工程替换依赖 2.4 注意事项 总结 引言 最近,因为开发的时候经改动依赖的库,所以,我想对 Gradle 脚本做一个调整,用来动态地将依赖替换为源码.这里以 android-mvvm-and-architecture 这个工程为例.该工程以依赖的形式引用了我的另一个工程 AndroidUtils.在之前,当我需要对 AndroidUtils 这个

  • Android透明化和沉浸式状态栏实践及源码分析

    本文所提到的透明状态栏其实指的是将顶部的导航栏延伸到状态栏,使之浑然一体(Google官方建议状态栏颜色比导航栏的颜色略深一点),并不代表一定不设置背景色,比如导航栏是白色,则可设置状态栏为白色,视情况而定. 相比于iOS系统,Android系统对于状态栏的设置就显得稍微复杂了一点.Android系统提供了API 19以上对状态栏的设置接口,而直到API 23以上才提供对于icon颜色的设置,还有就是各家厂商(如魅族,小米等)对于状态栏的有自己的定制,对于需要使用浅色背景状态栏的应用,没处理好的

随机推荐