加入收藏 | 设为首页 | 会员中心 | 我要投稿 核心网 (https://www.hxwgxz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 建站 > 正文

你的也是我的。3例ko多线程,局部变量透传

发布时间:2019-08-25 16:53:28 所属栏目:建站 来源:小姐姐养的狗
导读:java中的threadlocal,是绑定在线程上的。你在一个线程中set的值,在另外一个线程是拿不到的。如果在threadlocal的平行线程中,创建了新的子线程,那么这里面的值是无法传递、共享的(先想清楚为什么再往下看)。这就是透传问题。 值在线程之间的透传,你可
副标题[/!--empirenews.page--]

java中的threadlocal,是绑定在线程上的。你在一个线程中set的值,在另外一个线程是拿不到的。如果在threadlocal的平行线程中,创建了新的子线程,那么这里面的值是无法传递、共享的(先想清楚为什么再往下看)。这就是透传问题。

你的也是我的。3例ko多线程,局部变量透传

值在线程之间的透传,你可以认为是一个bug,这些问题一般会比较隐蔽,但问题暴露的时候脾气却比较火爆,让人手忙脚乱,怀疑人生。

作为代码的掌舵者,我们必然不能忍受这种问题的蹂躏。本篇文章适合细看,我们拿出3个例子,通过编码手段说明解决此类bug的通用方式,希望能达到举一反三的效果。对于搞基础架构的同学,是必备知识点。

1、普通线程的ThreadLocal透传问题

2、sl4j MDC组件中ThreadLocal透传问题

3、Hystrix组件的透传问题

由于涉及代码比较多,xjjdog将这三个例子的代码,放在了github上,想深入研究,可以下载下来debug一下。

  1. https://github.com/xjjdog/example-pass-through 

一、问题简单演示

为了有个比较直观的认识,下面展示一段异常代码。

你的也是我的。3例ko多线程,局部变量透传

以上代码在主线程设置了一个简单的threadlocal变量,然后在自线程中想要取出它的值。执行后发现,程序的输出是:null。

程序的输出和我们的期望产生了明显的差异。其实,将ThreadLocal 换成InheritableThreadLocal 就ok了。不要高兴太早,对于使用线程池的情况,由于会缓存线程,线程是缓存起来反复使用的。这时父子线程关系的上下文传递,已经没有意义。

二、解决线程池透传问题

所以,线程池InheritableThreadLocal进行提交,获取的值,有可能是前一个任务执行后留下的,是错误的。使用只有在任务执行的时候进行传递,才是正常的功能。

上面的问题,transmittable-thread-local项目,已经很好的解决,并提供了java-agent的方式支持。

我们这里从最小集合的源码层面,来看一下其中的内容。首先,我们看一下ThreadLocal的结构。

你的也是我的。3例ko多线程,局部变量透传

ThreadLocal其实是作为一个Map中的key而存在的,这个Map就是ThreadLocalMap,它以私有变量的形式,存在于Thread类中。拿上图为例,如果我创建了一个ThreadLocal,然后调用set方法,它会首先找到当前的thread,然后找到threadLocals,最后把自己作为key,存放在这个map里。

  1. hread t = Thread.currentThread(); 
  2. ThreadLocalMap map = getMap(t); 
  3. map.set(this, value); 

要能够完成多线程的协调工作,必须提供全套的多线程工具。包括但不限于:

1、定义注解,以及被注解修饰的ThreadLocal类

你的也是我的。3例ko多线程,局部变量透传

定义新的ThreadLocal类,以便在赋值的时候,能够根据注解进行拦截和过滤。这就要求,在定义ThreadLocal的时候,要使用我们提供的ThreadLocal类,而不是jdk提供的那两个。

2、进行父子线程之间的数据拷贝

在线程池提交任务之前,我们需要有个地方,将父进程的ThreadLocal内容,暂存一下。

你的也是我的。3例ko多线程,局部变量透传

由于很多变量都是private的,需要根据反射进行操作。根据上面提供的ThreadLocal类的结构,我们需要直接操作其中的变量table(这也是为什么jdk不能随便改变变量名的原因)。

将父线程相关的变量暂存之后,就可以在使用的时候,通过主动设值和清理,完成变量拷贝。

3、提供专用的Callable或者Runnable

那么这些数据是如何组装起来的呢?还是靠我们的任务载体类。

线程池提交线程,一般是通过Callable或者Runnable,以Runnable为例,我们看一下这个调用关系。

以下类采用了委托模式。

你的也是我的。3例ko多线程,局部变量透传

这样,只要在提交任务的时候,使用了我们自定义的Runnable;同时,使用了自定义的ThreadLocal,就能够正常完成透传。

三、解决MDC透传问题

sl4j MDC机制非常好,通常用于保存线程本地的“诊断数据”然后有日志组件打印,其内部时基于threadLocal实现;不过这就有一些问题,主线程中设置的MDC数据,在其子线程(多线程池)中是无法获取的,下面就来介绍如何解决这个问题。

!MDC ( Mapped Diagnostic Contexts ),它是一个线程安全的存放诊断日志的容器。通常,会在处理请求前将请求的唯一标示放到MDC容器中,比如sessionId。这个唯一标示会随着日志一起输出。配置文件可以使用占位符进行变量替换。

类似于上面介绍的方式,我们需要提供专用的Callable和Runnable。另外,为了能够同时支持MDC和普通线程,这两个类采用装饰器模式,进行功能追加。就单个类来说,对外的展现依然是委托模式。

你的也是我的。3例ko多线程,局部变量透传

同样的思路,同样的模式。不一样的是,父线程的信息暂存,我们直接使用MDC的内部方法,并在任务的执行前后,进行相应操作。

四、解决Hystrix透传问题

同样的问题,在Netflix公司的熔断组件Hystrix中,依然存在。Hystrix线程池模式下,透传ThreadLocal需要进行改造,它本身是无法完成这个功能的。

但是Hystrix策略无法简单通过yml文件方式配置。我们参考Spring Cloud中对此策略的扩展方式,开发自己的策略。需要继承HystrixConcurrentStrategy。

(编辑:核心网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读