TransmittableThreadLocal线程变量传递问题

ThreadLocal有时挺好用的,但是在某些情况下需要将父线程中的本地变量传递给子线程时,这个就不行了。
那么TransmittableThreadLocal就是为了解决这个问题的,但是,如果在某些异步的场景中,特别是异步线程是下载文件等耗费时长比较长的场景中,父线程结束了,那么在父线程清掉了本地变量的情况下(如果不清,可能会引起内存泄露),子线程中还需要用到父线程带过来的本地变量(比如说用户信息等),怎么办?
这里有个解决的办法。

1.首先写一个Controller类

这里主要就是模拟主线程,从CommonUtils(CommonUtils中的内容后面有)中塞入的可传递的本地线程变量中获取请求头中的empNo,然后将整个线程变量对象传递给Service中的方法,由于Service中的处理方法是异步的,就是传递给异步子线程。

package com.zte.csc;import com.zte.csc.base.util.CommonUtils;
import com.zte.springbootframe.common.model.ServiceData;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;/*** TransmittableThreadLocal线程变量传递问题* @author: xiaohuihui* @date: 2024/12/22*/
@RestController("testTTL")
@RequestMapping("/testTTLService")
@Slf4j
public class TestTTL {@Autowiredprivate TestTTLService testTTLService;@ApiOperation("测试TTL-Get方式")@RequestMapping(value = "/testTTL", method = RequestMethod.GET, produces = "application/json")public ServiceData queryTechnicalAdviceList() throws Exception {ServiceData sd = new ServiceData();try {String empNo = CommonUtils.getEmpNo();log.info("主线程中获取的empNo:{}", empNo);// 模拟异步长时间处理,传入TTL变量testTTLService.asyncProcess(CommonUtils.getSysGlobalConstVo());log.info("主线程处理结束");} catch (Exception e) {log.error("异常:{}", e.getMessage(), e);}sd.setCode(ServiceData.RetCode.Success);sd.setBo("返回成功");return sd;}}

2.然后写Service方法

Service中的方法为异步方式,入参就是父线程传递过来的全局线程变量SysGlobalConstVo。

这里主要验证几个事情:

  1. 子线程中如果模拟长时间操作,休眠线程10s,发现子线程中获取不到全局线程变量的值了(因为主线程已经回收了)
  2. 传递过来的全局线程变量的对象,如果在子线程中又重新放入全局线程变量,那么在子线程中后面的操作中也是可以获取到这个全局线程变量的值的
  3. 子线程中就算再加子线程操作,在下面的子线程中同样能拿到这个子线程塞进去的全局线程变量的值

这里有个注意的点:
子线程在重新塞了全局线程变量后,最后要remove,防止内存泄露。

package com.zte.csc;import com.google.common.collect.Lists;
import com.zte.csc.base.util.CommonUtils;
import com.zte.springbootframe.common.consts.SysGlobalConstVo;
import com.zte.springbootframe.common.exception.BusiException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;import java.util.List;/*** TransmittableThreadLocal线程变量传递问题* @author: xiaohuihui* @date: 2024/12/22*/
@Service
@Slf4j
public class TestTTLService {@Asyncpublic void asyncProcess(SysGlobalConstVo sysGlobalConstVo) throws BusiException {// 获取父线程传递的empNolog.info("子线程中获取到的父线程传递的empNo:{}", sysGlobalConstVo.getXEmpNo());try {// 模拟处理时间10sThread.sleep(10000);} catch (InterruptedException e) {log.error("异步处理线程异常:{}", e.getMessage(), e);}try {// 后面的某个地方再获取empNo, 发现这里是取不到的log.info("子线程中取到的empNo为:{}", CommonUtils.getEmpNo());// 父线程结束后,线程变量被清空,子线程通过CommonUtils.getEmpNo()方法拿不到值了,重新塞进去CommonUtils.setSysGlobalConstVo(sysGlobalConstVo);// 然后其他地方就可以获取到了log.info("重新塞值后子线程中取到的empNo为:{}", CommonUtils.getEmpNo());// 继续模拟下面还有子线程的情况List<Integer> list = Lists.newArrayList(1, 2, 3, 4, 5, 6);list.parallelStream().forEach(item -> {log.info("{}并发子线程中获取的empNo:{}", item, CommonUtils.getEmpNo());});throw new BusiException("0001" ,"特意抛出异常");}catch (Exception e){log.error("处理异常:{}", e.getMessage(), e);throw e;}finally {// 最后还是要清空,防止内存泄露CommonUtils.removeThreadLocal();}}
}

3.打印的结果

这里显示的是上面程序打印的结果,验证上面的逻辑。

2024-12-22 17:27:16.624 |-INFO  [http-nio-8084-exec-5] com.zte.csc.base.interceptor.CommonHandlerInterceptorAdapter [70] -| [00292987-20241222172716-133] preHandle HTTP_HEADER_X_EMP_NO : 00292987; HTTP_HEADER_X_LANG_ID : null,URI : /zte-crm-notify-basicservice/testTTLService/testTTL
2024-12-22 17:27:16.676 |-INFO  [http-nio-8084-exec-5] com.zte.csc.TestTTL [30] -| [00292987-20241222172716-133] 主线程中获取的empNo:00292987
2024-12-22 17:27:19.753 |-INFO  [metricsThreadPoolTaskScheduler-1] com.zte.csc.TestTTLService [25] -| [system] 子线程中获取到的父线程传递的empNo:00292987
2024-12-22 17:27:18.746 |-INFO  [http-nio-8084-exec-5] com.zte.csc.TestTTL [33] -| [00292987-20241222172716-133] 主线程处理结束
2024-12-22 17:27:19.928 |-INFO  [http-nio-8084-exec-5] com.zte.csc.base.interceptor.CommonHandlerInterceptorAdapter [88] -| [00292987-20241222172716-133] afterCompletion HTTP_HEADER_X_EMP_NO : 00292987; HTTP_HEADER_X_LANG_ID : zh_CN,URI : /zte-crm-notify-basicservice/testTTLService/testTTL
2024-12-22 17:27:29.917 |-INFO  [metricsThreadPoolTaskScheduler-1] com.zte.csc.TestTTLService [34] -| [system] 子线程中取到的empNo为:
2024-12-22 17:27:29.917 |-INFO  [metricsThreadPoolTaskScheduler-1] com.zte.csc.TestTTLService [38] -| [system] 重新塞值后子线程中取到的empNo为:00292987
2024-12-22 17:27:29.918 |-INFO  [metricsThreadPoolTaskScheduler-1] com.zte.csc.TestTTLService [42] -| [system] 4并发子线程中获取的empNo:00292987
2024-12-22 17:27:29.918 |-INFO  [ForkJoinPool.commonPool-worker-7] com.zte.csc.TestTTLService [42] -| [system] 6并发子线程中获取的empNo:00292987
2024-12-22 17:27:29.918 |-INFO  [ForkJoinPool.commonPool-worker-6] com.zte.csc.TestTTLService [42] -| [system] 2并发子线程中获取的empNo:00292987
2024-12-22 17:27:29.918 |-INFO  [ForkJoinPool.commonPool-worker-0] com.zte.csc.TestTTLService [42] -| [system] 1并发子线程中获取的empNo:00292987
2024-12-22 17:27:29.918 |-INFO  [ForkJoinPool.commonPool-worker-7] com.zte.csc.TestTTLService [42] -| [system] 5并发子线程中获取的empNo:00292987
2024-12-22 17:27:29.918 |-INFO  [ForkJoinPool.commonPool-worker-6] com.zte.csc.TestTTLService [42] -| [system] 3并发子线程中获取的empNo:00292987
2024-12-22 17:27:30.957 |-ERROR [metricsThreadPoolTaskScheduler-1] com.zte.csc.TestTTLService [47] -| [system] 处理异常:特意抛出异常
com.zte.springbootframe.common.exception.BusiException: 特意抛出异常at com.zte.csc.TestTTLService.asyncProcess(TestTTLService.java:45)at com.zte.csc.TestTTLService$$FastClassBySpringCGLIB$$15698846.invoke(<generated>)at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:792)at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:762)at org.springframework.aop.interceptor.AsyncExecutionInterceptor.lambda$invoke$0(AsyncExecutionInterceptor.java:115)at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)at java.util.concurrent.FutureTask.run(FutureTask.java)at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180)at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)at java.lang.Thread.run(Thread.java:748)
2024-12-22 17:27:42.074 |-ERROR [metricsThreadPoolTaskScheduler-1] org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler [39] -| [system] Unexpected exception occurred invoking async method: public void com.zte.csc.TestTTLService.asyncProcess(com.zte.springbootframe.common.consts.SysGlobalConstVo) throws com.zte.springbootframe.common.exception.BusiException

4.CommonUtils中的获取和设置本地变量的方法

这里用的就是可传递的线程变量:TransmittableThreadLocal

public class CommonUtils {private static TransmittableThreadLocal<SysGlobalConstVo> sysGlobalInfoThreadLocal = new TransmittableThreadLocal();private static TransmittableThreadLocal<UserInfoVO> userInfoVOThreadLocal = new TransmittableThreadLocal();private static TransmittableThreadLocal<HttpServletRequest> httpServletRequestThreadLocal = new TransmittableThreadLocal();private static TransmittableThreadLocal<HttpServletResponse> httpServletResponseThreadLocal = new TransmittableThreadLocal();public CommonUtils() {}public static void setSysGlobalConstVo(SysGlobalConstVo sysGlobalConstVo) {sysGlobalInfoThreadLocal.set(sysGlobalConstVo);}public static void setUserInfoVO(UserInfoVO userInfoVO) {userInfoVOThreadLocal.set(userInfoVO);}public static void setHttpServletRequest(HttpServletRequest request) {httpServletRequestThreadLocal.set(request);}public static void setHttpServletResponse(HttpServletResponse response) {httpServletResponseThreadLocal.set(response);}public static void removeThreadLocal() {sysGlobalInfoThreadLocal.remove();userInfoVOThreadLocal.remove();httpServletRequestThreadLocal.remove();httpServletResponseThreadLocal.remove();}public static String getUserLanguage() {return StringUtils.substring(getRequestLangId(), 0, 2);}public static HttpServletRequest getHttpServletRequest() {return (HttpServletRequest)httpServletRequestThreadLocal.get();}public static HttpServletResponse getHttpServletResponse() {return (HttpServletResponse)httpServletResponseThreadLocal.get();}public static UserInfoVO getUserInfoVO() {return (UserInfoVO)userInfoVOThreadLocal.get();}public static SysGlobalConstVo getSysGlobalConstVo() {return (SysGlobalConstVo)sysGlobalInfoThreadLocal.get();}public static String getUserId() {return null == userInfoVOThreadLocal.get() ? "" : ((UserInfoVO)userInfoVOThreadLocal.get()).getGuid();}public static String getEmpNo() {return null == userInfoVOThreadLocal.get() ? getRequestEmpNo() : ((UserInfoVO)userInfoVOThreadLocal.get()).getIdCardAb();}

5.拦截器中对请求头中信息的处理及和线程变量的交互

拦截器中主要是前置处理方法和后置处理方法。

前置处理方法中,获取请求头中的请求参数,并通过CommonUtils.setSysGlobalConstVo(vo)方法设置进全局线程变量中;

后置处理方法中,主要是通过CommonUtils.removeThreadLocal()方法清除全局线程变量。

public class CommonHandlerInterceptorAdapter extends HandlerInterceptorAdapter {private static final Logger log = LoggerFactory.getLogger(CommonHandlerInterceptorAdapter.class);private BiFunction<String, String, UserInfoVO> getUserFunction;public CommonHandlerInterceptorAdapter() {}public void setGetUserFunction(BiFunction<String, String, UserInfoVO> getUserFunction) {this.getUserFunction = getUserFunction;}public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {CommonUtils.setHttpServletRequest(request);CommonUtils.setHttpServletResponse(response);String xEmpNo = request.getHeader("X-Emp-No");String xLangId = request.getHeader("X-Lang-Id");String xAuthValue = request.getHeader("X-Auth-Value");String xOrgId = request.getHeader("X-Org-Id");String xOriginServiceName = request.getHeader("X-Origin-ServiceName");String xTenantId = request.getHeader("X-Tenant-Id");String xAppId = request.getHeader("X-App-Id");String xTimestamp = request.getHeader("X-Timestamp");String xEncryptedSign = request.getHeader("X-Encrypted-Sign");String xTraceId = MyLogUtils.getOrGenerateTraceId(request);SysGlobalConstVo vo = new SysGlobalConstVo();vo.setXEmpNo(xEmpNo);vo.setXLangId(xLangId);vo.setXAuthValue(xAuthValue);vo.setXOrgId(xOrgId);vo.setXOriginServiceName(xOriginServiceName);vo.setXTenantId(xTenantId);vo.setXAppId(xAppId);vo.setXTimestamp(xTimestamp);vo.setXEncryptedSign(xEncryptedSign);vo.setXTraceId(xTraceId);CommonUtils.setSysGlobalConstVo(vo);MDC.put("trace_id", xTraceId);response.setHeader("X-Trace-Id", StringEscapeUtils.escapeHtml4(xTraceId));log.info("preHandle HTTP_HEADER_X_EMP_NO : {}; HTTP_HEADER_X_LANG_ID : {},URI : {}", new Object[]{xEmpNo, xLangId, request.getRequestURI()});if (this.getUserFunction != null && StringUtils.isNotBlank(xEmpNo)) {String langId = StringUtils.startsWithIgnoreCase(xLangId, "en") ? "en" : "zh";try {UserInfoVO userInfoVO = (UserInfoVO)this.getUserFunction.apply(xEmpNo, langId);CommonUtils.setUserInfoVO(userInfoVO);} catch (Exception var17) {log.error(xEmpNo + "queryUserByIdCardAb error:" + var17.getMessage());}}return super.preHandle(request, response, handler);}public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {log.info("afterCompletion HTTP_HEADER_X_EMP_NO : {}; HTTP_HEADER_X_LANG_ID : {},URI : {}", new Object[]{CommonUtils.getRequestEmpNo(), CommonUtils.getRequestLangId(), request.getRequestURI()});CommonUtils.removeThreadLocal();MDC.remove("trace_id");super.afterCompletion(request, response, handler, ex);}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/64794.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

浅析InnoDB引擎架构(已完结)

大家好&#xff0c;我是此林。 今天来介绍下InnoDB底层架构。 1. 磁盘架构 我们所有的数据库文件都保存在 /var/lib/mysql目录下。 由于我这边是docker部署的mysql&#xff0c;用如下命令查看mysql数据挂载。 docker inspect mysql-master 如下图&#xff0c;目前只有一个数…

Ajax中的axios

既然提到Ajax&#xff0c;那就先来说一说什么是Ajax吧 关于Ajax Ajax的定义 Asynchronous JavaScript And XML&#xff1a;异步的JavaScript和XML。 反正就是一句话总结&#xff1a; 使用XML HttpRequest 对象与服务器进行通讯。 AJAX 是一种在无需重新加载整个网页的情况下&…

苹果手机怎么清理空间:拯救你的拥挤手机

在数字生活的海洋中&#xff0c;我们的苹果手机就像一艘小船&#xff0c;载满了照片、应用、视频和各种下载的“宝贝”。随着时间的推移&#xff0c;这艘小船开始变得拥挤&#xff0c;航行速度放缓&#xff0c;甚至有时候直接卡壳。苹果手机怎么清理空间&#xff1f;是时候学会…

三、使用langchain搭建RAG:金融问答机器人--检索增强生成

经过前面2节数据准备后&#xff0c;现在来构建检索 加载向量数据库 from langchain.vectorstores import Chroma from langchain_huggingface import HuggingFaceEmbeddings import os# 定义 Embeddings embeddings HuggingFaceEmbeddings(model_name"m3e-base")#…

C语言 函数嵌套

#include <stdio.h> void new_line() {printf("hehe\n"); } void three_line() {int i 0;for (i 0; i < 3; i){new_line;} } int main() {three_line();return 0; } 函数可以嵌套调用&#xff0c;但不能嵌套定义 链式访问 main有三个参数 //main函数的…

问题解决:发现Excel中的部分内容有问题。是否让我们尽量尝试恢复? 如果您信任此工作簿的源,请单击“是”。

在开发同步导出功能是遇到了如标题所示的问题&#xff0c;解决后遂记录下来供大家参考。 RestController public class XxxController {PostMapping("/export")public BaseResponse export(RequestBody PolicyErrorAnalysisExportReq exportReq, HttpServletRespons…

基于ST STM32MP257FAK3的MP2控制器之工业PLC 方案

简介 1.可编程逻辑控制器&#xff08;PLC&#xff09;是种专门为在工业环境下应用而设计的数字运算操作电子系统。它采用一种可编程的存储器&#xff0c;在其内部存储执行逻辑运算、顺序控制、定时、计数和算术运算等操作的指令&#xff0c;通过数字式或模拟式的输入输出来控制…

golang自定义MarshalJSON、UnmarshalJSON 原理和技巧

问题出现的原因&#xff1a;在前后端分离的项目中&#xff0c;经常出现的问题是时间戳格式的问题。 后端的日期格式兼容性强&#xff0c;比较完善。前端由于各种原因&#xff0c;日期格式不完善。 就会产生矛盾。 ms int64比较通用&#xff0c;但是unix时间没有可读性&#xff…

初始Python篇(7)—— 正则表达式

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a; Python 目录 正则表达式的概念 正则表达式的组成 元字符 限定符 其他字符 正则表达式的使用 正则表达式的常见操作方法 match方法的…

使用 AI 辅助开发一个开源 IP 信息查询工具:一

本文将分享如何借助当下流行的 AI 工具,一步步完成一个开源项目的开发。 写在前面 在写代码时&#xff0c;总是会遇到一些有趣的机缘巧合。前几天&#xff0c;我在翻看自己之前的开源项目时&#xff0c;又看到了 DDNS 相关的讨论。虽然在 2021 年我写过两篇相对详细的教程&am…

Powershell学习笔记

声明&#xff01; 学习视频来自B站up主 **泷羽sec** 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&a…

《Java源力物语》-2.异常训练场

~犬&#x1f4f0;余~ “我欲贱而贵&#xff0c;愚而智&#xff0c;贫而富&#xff0c;可乎&#xff1f; 曰&#xff1a;其唯学乎” \quad 在java.lang古域的一处偏僻角落&#xff0c;矗立着一座古老的训练场。青灰色的围墙上布满了密密麻麻的源力符文&#xff0c;这些符文闪烁着…

一起学Git【第二节:创建版本库】

创建库 这个库相当于一个目录&#xff0c;目录中的文件都被Git管理&#xff0c;会记录每个文件的修改删除和添加工作&#xff0c;便于之后随时跟踪历史记录还原到之前的某一版本。如何创建库呢&#xff1f;有两种方式&#xff0c;本地创建库和云端克隆一个库。 1.本地创建库 …

HarmonyOS NEXT 技术实践-基于基础视觉服务的多目标识别

在智能手机、平板和其他智能设备日益智能化的今天&#xff0c;视觉识别技术成为提升用户体验和智能交互的重要手段。HarmonyOS NEXT通过基础视觉服务&#xff08;HMS Core Vision&#xff09;提供了一套强大的视觉识别功能&#xff0c;其中多目标识别作为其关键技术之一&#x…

nginx-静态资源部署

目录 静态资源概述 静态资源配置指令 listen指令 server_name指令 精确匹配 ​编辑 ​编辑 使用通配符匹配 使用正则表达式匹配 匹配执行顺序 default_server属性 location指令 root指令 alias指令 root与alisa指令的区别 index指令 error_page指令 直接使用 …

时空信息平台架构搭建:基于netty封装TCP通讯模块(IdleStateHandler网络连接监测,处理假死)

文章目录 引言I 异步TCP连接操作II 心跳机制:空闲检测(读空闲和写空闲)基于Netty的IdleStateHandler类实现心跳机制(网络连接监测)常规的处理假死健壮性的处理假死方案获取心跳指令引言 基于netty实现TCP客户端:封装断线重连、连接保持 https://blog.csdn.net/z92911896…

Linux之RPM和YUM命令

一、RPM命令 1、介绍 RPM(RedHat Package Manager).,RedHat软件包管理工具&#xff0c;类似windows里面的setup,exe是Liux这系列操作系统里而的打包安装工具。 RPMI包的名称格式&#xff1a; Apache-1.3.23-11.i386.rpm “apache’” 软件名称“1.3.23-11” 软件的版本号&am…

aosp15 - Activity生命周期切换

本文探查的是&#xff0c;从App冷启动后到MainActivity生命周期切换的系统实现。 调试步骤 在com.android.server.wm.RootWindowContainer#attachApplication 方法下断点&#xff0c;为了attach目标进程在com.android.server.wm.ActivityTaskSupervisor#realStartActivityLock…

【漫话机器学习系列】017.大O算法(Big-O Notation)

大 O 表示法&#xff08;Big-O Notation&#xff09; 大 O 表示法是一种用于描述算法复杂性的数学符号&#xff0c;主要用于衡量算法的效率&#xff0c;特别是随着输入规模增大时算法的运行时间或占用空间的增长趋势。 基本概念 时间复杂度 描述算法所需的运行时间如何随输入数…

ensp 基于端口安全的财务部网络组建

ARP IP数据包通过以太网发送&#xff0c;但以太网设备并不能识别IP地址&#xff0c;它们是以MAC地址传输的。因此&#xff0c;必须把IP目的地址转换成MAC目的地址。在以太网中&#xff0c;一个主机要和另一个主机进行直接通信&#xff0c;必须要知道目标主机的MAC地址。 ARP&…