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

2024-03-01 05:30

本文主要是介绍你的也是我的。3例ko多线程,局部变量透传,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

640?wx_fmt=gif

原创:小姐姐味道(微信公众号ID:xjjdog),欢迎分享,转载请保留出处。

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

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

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

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

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

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

一、问题简单演示

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

640?wx_fmt=png

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

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

二、解决线程池透传问题

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

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

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

640?wx_fmt=png

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

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

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

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

640?wx_fmt=png

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

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

640?wx_fmt=png

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

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

3、提供专用的Callable或者Runnable

以下类采用了委托模式。640?wx_fmt=png

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

三、解决MDC透传问题

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

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

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

640?wx_fmt=png

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

四、解决Hystrix透传问题

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

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

构造代码还是较长的,可以查看github项目。但有一个地方需要说明。

640?wx_fmt=png

我们使用装饰器模式,对代码进行了层层嵌套,同时将多线程透传功能、MDC传递功能给追加了进来。这样,我们的这个类,就同时在以上三个环境中拥有了透传功能。

End

同样的思路,可以用在其他组件上。比如我们在多篇调用链的文章里,提到的trace信息在多线程环境下的传递。

一般就是在当前线程暂存数据,然后在提交任务时进行包装。值得注意的是,这种方式侵入性还是比较大的,适合封装在通用的基础工具包中。你要是在业务中这么用,大概率会被骂死。

那可如何是好。

ThreadLocal会引发很多棘手的bug,造成代码污染。在使用之前,一定要确保你确实需要使用它。比如你在SimpleDateFormat类上用了线程局部变量,可以将它替换成DateTimeFormatter。

我们不善于解决问题,我们只善于解决容易出问题的类。

作者简介:小姐姐味道  (xjjdog),一个不允许程序员走弯路的公众号。聚焦基础架构和Linux。十年架构,日百亿流量,与你探讨高并发世界,给你不一样的味道。我的个人微信xjjdog0,欢迎添加好友,进一步交流。

近期热门文章

《必看!java后端,亮剑诛仙》

《Linux上,最常用的一批命令解析(10年精选)》

《这次要是讲不明白Spring Cloud核心组件,那我就白编这故事了》

《Linux生产环境上,最常用的一套“Sed“技巧》

640?wx_fmt=gif

这篇关于你的也是我的。3例ko多线程,局部变量透传的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/761344

相关文章

Android多线程下载见解

通过for循环开启N个线程,这是多线程,但每次循环都new一个线程肯定很耗内存的。那可以改用线程池来。 就以我个人对多线程下载的理解是开启一个线程后: 1.通过HttpUrlConnection对象获取要下载文件的总长度 2.通过RandomAccessFile流对象在本地创建一个跟远程文件长度一样大小的空文件。 3.通过文件总长度/线程个数=得到每个线程大概要下载的量(线程块大小)。

如何在Android中实现多线程与线程池?

目录 一、Android介绍二、什么是多线程三、什么是线程池四、如何在Android中实现多线程与线程池 一、Android介绍 Android是一种基于Linux内核的开源操作系统,由Google公司领导开发。它最初于2007年发布,旨在为移动设备提供一种统一、可扩展的操作系统。Android系统以其高度的可定制性和丰富的应用生态而受到广泛欢迎,如今已经成为全球最流行的

【c2】编译预处理,gdb,makefile,文件,多线程,动静态库

文章目录 1.编译预处理:C源程序 - 编译预处理【#开头指令和特殊符号进行处理,删除程序中注释和多余空白行】- 编译2.gdb调试:多进/线程中无法用3.makefile文件:make是一个解释makefile中指令的命令工具4.文件:fprint/fgets/fwrite/fread,ftell/rewind/fseek/fflush4.1 文本文件:FILE结构体4.2 二进制文件:没

多线程简单运用---时钟的实现

效果图:   代码: package game;import java.awt.BorderLayout;import java.awt.Color;import java.awt.Container;import java.awt.Font;import java.awt.event.ActionEvent;import java.awt.event.ActionListe

多线程的简单应用----彩票摇号器

效果图:   package game;import java.awt.BorderLayout;import java.awt.Container;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.util.Random;import javax.swing.JBu

通过简单的售票程序理解多线程

售票程序要求:1:多个窗口同时卖票  2:同一编号的票的不能多次卖出   错误程序解析: public class TicketDemo {public static void main(String[] args) {Ticket t1=new Ticket("窗口1:");Ticket t2=new Ticket("窗口2:");Ticket t3=new Ticket("窗口3

多线程相关方法详解

Thread中的方法 start()   用start() 方法来启动线程,真正实现了多线程运行,这时无需等待run() 方法内代码执行完毕,而是直接继续执行下面的代码。通过start()方法启动一个线程后,线程处于就绪状态,并没有运行,一旦得到CPU时间片,就开始执行run()方法。 注意:多次start()启动一个线程是非法的 run()   run()称为线程体,它包含了要执

如何保证单例模式在多线程中的线程安全性

如何保证单例模式在多线程中的线程安全性         对大数据、分布式、高并发等知识的学习必须要有多线程的基础。这里讨论一下如何在多线程的情况下设计单例模式。在23中设计模式中单例模式是比较常见的,在非多线程的情况下写单例模式,考虑的东西会很少,

[实践篇]13.29 再来聊下Pass Through设备透传

写在前面 为什么要再聊天Pass Through? 因为在QNX + Linux Android的技术方案下,我们会遇到LA发生reboot或异常panic后,无法正常开机。而再次异常的原因确实最头疼的Memory Corruption。观察下来是由于一些DMA外设如使用UART的一些设备在重启或panic后,没有正常走Shutdown流程,导致Buffer仍然被使用。所以这里再进行一些总结。

static修饰全局变量,局部变量和函数用法

static作用(修饰函数、局部变量、全局变量) 在C语言中,static的字面意思很容易把我们导入歧途,其实它的作用有三条。 (1)先来介绍它的第一条也是最重要的一条:隐藏。 当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。为理解这句话,我举例来说明。我们要同时编译两个源文件,一个是a.c,另一个是main.c。 下面是a.c的内容 char  a