news 2026/4/18 7:29:56

详细分析线程池上下文的基本知识(附Demo)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
详细分析线程池上下文的基本知识(附Demo)

目录

  • 前言
  • 1. 基本知识
  • 2. 父子线程数据不同
  • 3. 父子线程数据相同

前言

Java基本知识:

  1. java框架 零基础从入门到精通的学习路线 附开源项目面经等(超全)
  2. 【Java项目】实战CRUD的功能整理(持续更新)

1. 基本知识

本意大概如下:
基于 ThreadLocal 的请求级校验上下文容器(ValidatorDataContext,这是自定义的),用于在同一个请求线程内共享和传递校验/业务相关数据。它继承自ConcurrentHashMap<String, Object>,本质上是一个线程安全的 Key-Value 上下文,但通过 ThreadLocal 绑定到当前线程,从而保证每个请求线程拥有独立的数据副本,互不干扰

整体设计目的是:在校验(Validator)或业务处理链路中,作为一个与请求生命周期一致的上下文数据容器,方便不同校验器或组件在不显式传参的情况下共享数据,同时保证线程安全与请求隔离。

**线程池上下文(Thread Pool Context)**指的是:
在任务提交线程中已有的一些“隐式状态”,如何在被线程池复用的工作线程中继续可用

“隐式状态”通常包括:

登录用户信息(userId、token)
请求信息(requestId、traceId)
租户信息(tenantId)
语言/区域(Locale)
安全上下文(SecurityContext)
日志链路(MDC / Trace)
数据源路由信息
事务标记(⚠️)
线程池上下文的Demo如下:

1️⃣ ThreadLocal 的设计前提
ThreadLocal 的核心假设是:
一个请求 = 一个线程

但在线程池中:
线程是 复用的
一个线程会执行 多个不同请求的任务

➡️ 导致两个问题:

❌ 问题一:上下文丢失

ThreadLocal.set("userA");executor.submit(()->{// 这里读不到 userA});

原因:
任务在线程池的另一个线程执行


❌ 问题二:上下文污染(更危险)

ThreadLocal.set("userA");executor.submit(()->{// 执行完后没清理});executor.submit(()->{// 读到的是 userA(脏数据)});

原因:
线程池线程被复用,上一个任务的 ThreadLocal 没清



线程池的上下文Demo:

package线程池上下文;importlombok.Data;importjava.util.concurrent.ConcurrentHashMap;@DatapublicclassValidatorDataContextextendsConcurrentHashMap<String,Object>{/** * * 请求对象 */publicObjectrequestDto;publicObjectput(Stringkey,Objectvalue){if(key==null||value==null){returnnull;}else{returnsuper.put(key,value);}}/** * * ValidatorContext为请求上下文,与当前请求线程绑定,继承自ConcurrentHashMap */publicstaticfinalThreadLocal<?extendsValidatorDataContext>threadLocal=ThreadLocal.withInitial(()->newValidatorDataContext());/** * * 获取当前线程的上下文 * * @return */publicstaticValidatorDataContextgetCurrentContext(){ValidatorDataContextcontext=threadLocal.get();returncontext;}/** * * 设值 * * @param key * @param value */publicvoidset(Stringkey,Objectvalue){if(value!=null){put(key,value);}else{remove(key);}}/** * 设置值 * @param clazz 会自动取SimpleName为Key * @param value * @author K * 2020年10月12日下午6:38:01 */publicvoidsetByClass(Classclazz,Objectvalue){Stringkey=clazz.getSimpleName();set(key,value);}/** * * 获取String值 * * @param key * @return */publicStringgetString(Stringkey){return(String)get(key);}/**其他类型的值同理,进行强转换*//** * * 获取对象 * * @param <T> * @return */public<T>TgetByClazz(Class<T>clazz){if(get(clazz.getSimpleName())==null){returnnull;}else{return(T)get(clazz.getSimpleName());}}public<T>TgetRequestDto(Class<T>clazz){returnclazz.cast(requestDto);// 强制类型转换,更安全}publicvoidsetRequestDto(ObjectrequestDto){this.requestDto=requestDto;}}

2. 父子线程数据不同

子线程不会拷贝父线程的数据!

package线程池上下文;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;importjava.util.concurrent.TimeUnit;// 模拟一个业务对象classUser{privateStringname;privateintage;publicUser(Stringname,intage){this.name=name;this.age=age;}publicStringgetName(){returnname;}publicintgetAge(){returnage;}}publicclassValidatorDataContextDemo{publicstaticvoidmain(String[]args)throwsInterruptedException{// 创建线程池ExecutorServiceexecutor=Executors.newFixedThreadPool(2);// === 主线程:设置上下文 ===ValidatorDataContextcontext=ValidatorDataContext.getCurrentContext();context.set("traceId","TRACE-20251225-001");context.setRequestDto(newUser("Alice",30));context.setByClass(User.class,newUser("Bob",25));System.out.println("主线程 - traceId: "+context.getString("traceId"));System.out.println("主线程 - RequestDto name: "+context.getRequestDto(User.class).getName());System.out.println("主线程 - User class value name: "+context.getByClazz(User.class).getName());// === 在线程池中访问上下文 ===executor.submit(()->{// 注意:ThreadLocal 是线程隔离的,所以线程池线程无法直接获取主线程上下文ValidatorDataContextthreadContext=ValidatorDataContext.getCurrentContext();System.out.println("\n子线程1 - traceId: "+threadContext.getString("traceId"));// nullSystem.out.println("子线程1 - RequestDto: "+threadContext.getRequestDto());// null// 可以在子线程独立设置threadContext.set("traceId","THREAD-TRACE-001");System.out.println("子线程1 - 新 traceId: "+threadContext.getString("traceId"));});executor.submit(()->{ValidatorDataContextthreadContext=ValidatorDataContext.getCurrentContext();System.out.println("\n子线程2 - traceId: "+threadContext.getString("traceId"));// null});// 等待线程执行完executor.shutdown();executor.awaitTermination(5,TimeUnit.SECONDS);// === 主线程上下文依然可用 ===System.out.println("\n主线程 - traceId 再次访问: "+context.getString("traceId"));}}

截图如下:

3. 父子线程数据相同

在大的Demo下增加如下:

// 克隆当前上下文publicValidatorDataContextcopy(){ValidatorDataContextcopy=newValidatorDataContext();copy.putAll(this);copy.requestDto=this.requestDto;returncopy;}

总体的Demo测试如下:

package线程池上下文;importjava.util.concurrent.*;// 包装 Runnable,传递上下文classContextAwareRunnableimplementsRunnable{privatefinalRunnabletask;privatefinalValidatorDataContextcapturedContext;publicContextAwareRunnable(Runnabletask,ValidatorDataContextcontext){this.task=task;this.capturedContext=context.copy();// 拷贝上下文}@Overridepublicvoidrun(){ValidatorDataContextprevious=ValidatorDataContext.getCurrentContext();try{// 设置当前线程上下文为捕获的上下文ValidatorDataContext.threadLocal.set(capturedContext);task.run();}finally{// 执行完毕,恢复或清理上下文ValidatorDataContext.threadLocal.set(previous);}}}// 主类 DemopublicclassValidatorDataContextWithThreadPoolDemo{// 辅助方法:包装 RunnableprivatestaticRunnablewrap(Runnabletask){returnnewContextAwareRunnable(task,ValidatorDataContext.getCurrentContext());}publicstaticvoidmain(String[]args)throwsInterruptedException,ExecutionException{ExecutorServiceexecutor=Executors.newFixedThreadPool(2);// 主线程设置上下文ValidatorDataContextcontext=ValidatorDataContext.getCurrentContext();context.set("traceId","TRACE-20251225-001");context.setRequestDto(newUser("Alice",30));context.setByClass(User.class,newUser("Bob",25));System.out.println("主线程 - traceId: "+context.getString("traceId"));System.out.println("主线程 - RequestDto name: "+context.getRequestDto(User.class).getName());System.out.println("主线程 - User class value name: "+context.getByClazz(User.class).getName());// 提交线程池任务(自动继承主线程上下文)Future<?>future1=executor.submit(wrap(()->{ValidatorDataContextthreadContext=ValidatorDataContext.getCurrentContext();System.out.println("\n子线程1 - traceId: "+threadContext.getString("traceId"));System.out.println("子线程1 - RequestDto name: "+threadContext.getRequestDto(User.class).getName());System.out.println("子线程1 - User class value name: "+threadContext.getByClazz(User.class).getName());// 修改上下文不会影响主线程threadContext.set("traceId","THREAD-TRACE-001");}));Future<?>future2=executor.submit(wrap(()->{ValidatorDataContextthreadContext=ValidatorDataContext.getCurrentContext();System.out.println("\n子线程2 - traceId: "+threadContext.getString("traceId"));}));// 等待执行完成future1.get();future2.get();// 主线程上下文仍然保持原值System.out.println("\n主线程 - traceId 再次访问: "+context.getString("traceId"));executor.shutdown();executor.awaitTermination(5,TimeUnit.SECONDS);}}

截图如下:

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 7:04:31

基于AI智能名片链动2+1模式S2B2C商城小程序的商户端微商平台构建研究

摘要&#xff1a;在数字化商业浪潮下&#xff0c;商户端微商面临激烈竞争&#xff0c;需构建全面且高效的平台体系。本文聚焦AI智能名片链动21模式S2B2C商城小程序在商户端微商平台构建中的应用&#xff0c;从技术、宣传、资源三个平台维度展开研究。通过分析该模式在各平台的作…

作者头像 李华
网站建设 2026/4/18 8:20:45

专注充电桩投资,招募城市合伙人 - 慧知开源充电桩平台

专注充电桩投资&#xff0c;招募城市合伙人 - 慧知开源充电桩平台 我们提供资本与战略&#xff0c;您负责落地与执行。本团队的核心业务是 投资建设充电桩&#xff0c;并作为您的投资人兼战略顾问&#xff1a;投入全部资金&#xff0c;并在选址、技术、资源等关键环节提供支持&…

作者头像 李华
网站建设 2026/4/18 6:24:01

Java毕设选题推荐:基于vue+协同过滤算法的动漫推荐系统热门动漫浏览、文章专栏阅读【附源码、mysql、文档、调试+代码讲解+全bao等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/4/18 6:30:52

两种常见开关中断方式对比

经常会遇到两种典型的中断禁用 / 启用实现&#xff1a;一种是基于纯汇编编写的Arch_IntSave/Arch_IntDisable函数&#xff0c;另一种是编译器内置的__enable_irq/__disable_irq内联函数&#xff0c;这两种的区别和功能具体拆解一下。一、核心实现与功能拆解1. 纯汇编实现&#…

作者头像 李华
网站建设 2026/4/18 6:29:10

城市仿真软件:CityEngine_(11).城市规划与设计实践

城市规划与设计实践 在上一节中&#xff0c;我们探讨了城市仿真软件的基本功能和应用场景。接下来&#xff0c;我们将深入到城市规划与设计的实践部分&#xff0c;通过具体的案例和代码示例&#xff0c;展示如何利用CityEngine进行城市模型的创建、编辑和仿真。本节将涵盖以下…

作者头像 李华
网站建设 2026/4/18 8:47:46

MySQL介绍

一、MySQL1. MySQL是什么&#xff1f;&#xff08;简单直白版&#xff09;想象一下&#xff0c;你开了一家超级棒的奶茶店&#xff0c;每天要记录&#xff1a;顾客点的奶茶口味顾客的联系方式每天的销售数据仓库里的原料库存如果用Excel表格记录&#xff0c;当数据量达到10万条…

作者头像 李华