ThreadLocal 实战使用详解

ThreadLocal 知识储备传送门:

ThreadLocal 原理及源码详解

ThreadLocal 内存泄漏和常见问题详解

什么是ThreadLocal?

ThreadLocal 是一种提供线程本地变量(也称为线程局部变量)的类,这种变量确保了在不同的线程中访问同一个 ThreadLocal 变量时,每个线程会有一个该变量的私有副本,即使多个线程修改了相同的 ThreadLocal 变量,每个线程所访问的副本仍然是独立的,从而避免了多线程环境下共享变量可能导致的数据不一致和竞争问题。

ThreadLocal 的作用?

ThreadLocal 的主要作用在于线程隔离,ThreadLocal 中的数据只属于当前线程,对其他线程是不可见的,实现每一个线程都有自己线程本地变量,ThreadLocal 贯穿了线程的整个生命周期,在线程的生命周期中的任何地方都可以取出,这样这些本地变量就类似是全局变量了,也可以说 ThreadLocal 最大的用处就是把局部变量共享成全局变量。

既然 ThreadLocal 只是给每个线程创建了变量副本,方便变量副本在线程生命周期中的多个方法中使用,那我们 new 一下不就可以了吗,为什么还要使用 ThreadLocal ?

没错,如果不嫌麻烦,new 一个变量副本作为参数传递是完全可以的,但是对用这种全局变量,使用 ThreadLocal 来维护会大大降低维护成本。

ThreadLocal 的使用场景?

我们熟知的 Spring 框架中就大量使用了 ThreadLocal ,如下:

//我们非常熟悉的 spring 事务管理器
public abstract class TransactionSynchronizationManager {private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal("Transactional resources");private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal("Transaction synchronizations");private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal("Current transaction name");private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal("Current transaction read-only status");private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal("Current transaction isolation level");private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal("Actual transaction active");public TransactionSynchronizationManager() {}}public abstract class RequestContextHolder {private static final boolean jsfPresent = ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());private static final ThreadLocal<RequestAttributes> requestAttributesHolder = new NamedThreadLocal("Request attributes");private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder = new NamedInheritableThreadLocal("Request context");public RequestContextHolder() {}}

ThreadLocal 的使用场景

  • 场景一:每个线程需要独享一个局部对象(非常典型的 SimpleDateFormat 问题 )。
  • 场景二:每个线程内需要保存全局变量,即一个变量需要在线程整个生命周期中的不同方法直接使用,避免参数传递(非常常见的用户信息传递问题 )。

场景一:每个线程需要独享一个局部对象

我们知道多线程的情况下,多个线程使用同一个 SimpleDateFormat 对象会有线程安全问题。

演示代码如下:

public class ThreadLocalTest {//同一个 SimpleDateFormat对象private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static ExecutorService threadPool = Executors.newFixedThreadPool(100);public static void main(String[] args) {for (int a = 0; a < 100; a++) {threadPool.execute(() -> {//synchronized (ThreadLocalTest.class){Date date = null;try {date = dateFormat.parse("2024-04-13 12:12:12");} catch (ParseException e) {e.printStackTrace();}System.out.println(date);//}});}}}

执行结果如下:

Exception in thread "pool-1-thread-6" Exception in thread "pool-1-thread-2" Exception in thread "pool-1-thread-3" Exception in thread "pool-1-thread-7" Exception in thread "pool-1-thread-4" Exception in thread "pool-1-thread-1" java.lang.NumberFormatException: multiple pointsat sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)at java.lang.Double.parseDouble(Double.java:538)at java.text.DigitList.getDouble(DigitList.java:169)at java.text.DecimalFormat.parse(DecimalFormat.java:2056)at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)at java.text.DateFormat.parse(DateFormat.java:364)at com.zt.zteam.main.controller.auth.ThreadLocalTest.lambda$main$0(ThreadLocalTest.java:27)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:745)
Exception in thread "pool-1-thread-13" java.lang.NumberFormatException: empty Stringat sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)at java.lang.Double.parseDouble(Double.java:538)at java.text.DigitList.getDouble(DigitList.java:169)at java.text.DecimalFormat.parse(DecimalFormat.java:2056)at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)at java.text.DateFormat.parse(DateFormat.java:364)at com.zt.zteam.main.controller.auth.ThreadLocalTest.lambda$main$0(ThreadLocalTest.java:27)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:745)
java.lang.NumberFormatException: multiple pointsat sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)at java.lang.Double.parseDouble(Double.java:538)at java.text.DigitList.getDouble(DigitList.java:169)at java.text.DecimalFormat.parse(DecimalFormat.java:2056)at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)at java.text.DateFormat.parse(DateFormat.java:364)at com.zt.zteam.main.controller.auth.ThreadLocalTest.lambda$main$0(ThreadLocalTest.java:27)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:745)
java.lang.NumberFormatException: multiple pointsat sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)at java.lang.Double.parseDouble(Double.java:538)at java.text.DigitList.getDouble(DigitList.java:169)at java.text.DecimalFormat.parse(DecimalFormat.java:2056)at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)at java.text.DateFormat.parse(DateFormat.java:364)at com.zt.zteam.main.controller.auth.ThreadLocalTest.lambda$main$0(ThreadLocalTest.java:27)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:745)
java.lang.NumberFormatException: multiple pointsat sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890)at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)at java.lang.Double.parseDouble(Double.java:538)at java.text.DigitList.getDouble(DigitList.java:169)at java.text.DecimalFormat.parse(DecimalFormat.java:2056)at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)at java.text.DateFormat.parse(DateFormat.java:364)at com.zt.zteam.main.controller.auth.ThreadLocalTest.lambda$main$0(ThreadLocalTest.java:27)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:745)

很明显多线程并发的情况下,执行报错了。
当然上面的错误可以使用 synchronized 、ReentrantLock 可以实现,但是使用锁机制的话会让并行变为串行,一个个排队排队执行,会有性能问题。

synchronized 、ReentrantLock 传送门:
synchronized 使用及深入理解
深入理解 ReentrantLock 【源码分析】

下面我们改造上面的代码,使用 ThreadLocal 来实现,代码如下:

public class ThreadLocalTest {//ThreadLocalprivate static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {@Overrideprotected SimpleDateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");}};//线程池 方便发起多线程调用 真实业务开发不要这样使用线程池public static ExecutorService threadPool = Executors.newFixedThreadPool(100);public static void main(String[] args) {for (int a = 0; a < 100; a++) {threadPool.execute(() -> {Date date = null;try {date = dateFormatThreadLocal.get().parse("2024-04-13 12:12:12");} catch (ParseException e) {e.printStackTrace();}System.out.println(date);});}}
}

执行结果:

Sat Apr 13 12:12:12 CST 2024
Sat Apr 13 12:12:12 CST 2024
Sat Apr 13 12:12:12 CST 2024
Sat Apr 13 12:12:12 CST 2024
Sat Apr 13 12:12:12 CST 2024
Sat Apr 13 12:12:12 CST 2024
Sat Apr 13 12:12:12 CST 2024
.................................................

很明显,ThreadLocal 帮我们解决了 SimpleDateFormat 的问题。

场景二:每个线程内需要保存全局变量

当前线程的用户信息需要被线程内所有方法共享,也就是需要被线程内部的多个方法使用,比较直接也比较麻烦的解决方案是把用户信息一层层的传递下去,这显然不是一个好的解决方案,这里我们就可以使用 ThreadLocal 来解决了。

使用 ThreadLocal 来传递用户信息,思路很简单,我们在拦截器或者过滤器中对用户信息进行拦截处理后,设置到 ThreadLocal 中,后续的业务就可以使用用户信息了,伪代码如下:

UserContextHolder 类,对 ThreadLocal 稍加包装。

public class UserContextHolder {//ThreadLocal 对象private static final ThreadLocal<UserInfoVO> USER_THREAD_LOCAL = new ThreadLocal<>();/*** @Description: 设置用户信息* @Date: 2024/4/13 17:00*/public static  void setUser(UserInfoVO userInfoVO) {USER_THREAD_LOCAL.set(userInfoVO);}/*** @Description: 获取用户信息* @Date: 2024/4/13 17:00*/public static UserInfoVO getUser() {return USER_THREAD_LOCAL.get();}/*** @Description: 获取用户姓名* @Date: 2024/4/13 17:00*/public static String getUsername() {return USER_THREAD_LOCAL.get().getName();}/*** @Description: 获取用户id* @Date: 2024/4/13 17:00*/public static Long getUserId() {return USER_THREAD_LOCAL.get().getId();}/*** @Description: 获取用户工号* @Date: 2024/4/13 17:00*/public static String getUserCode() {return USER_THREAD_LOCAL.get().getCode();}/*** 清空上下文*/public static void remove() {USER_THREAD_LOCAL.remove();}}

拦截器或者过滤器,这里以过滤器举例,伪代码如下:

public class MyAuthenticationFilter implements Filter {@Overridepublic void init(FilterConfig filterConfig) throws ServletException {Filter.super.init(filterConfig);}@Overridepublic void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {UserInfoVO userInfoVO = new UserInfoVO();userInfoVO.setId(1L);userInfoVO.setCode("000001");userInfoVO.setName("张三");//设置用户信息UserContextHolder.setUser(userInfoVO);filterChain.doFilter(servletRequest, servletResponse);}@Overridepublic void destroy() {//将ThreadLocal数据清空 UserContextHolder.remove();Filter.super.destroy();}
}

测试验证代码:

@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {@GetMapping("/thread-local-test")@ApiOperation(httpMethod = "GET", value = "ThreadLocal测试", notes = "ThreadLocal测试")Result<UserInfoVO> threadLocalTest() {return ResultGenerator.genSuccessResult(UserContextHolder.getUser());}
}

测试结果:

{"code": 200,"message": "SUCCESS","data": {"id": 1,"code": "000001","name": "张三"}
}

根据结果可以知道达到了我们预期的结果,使用 ThreadLocal 传递用户信息十分简单方便,减少了参数传递,且代码结构十分清晰。

ThreadLocal 优缺点?

优点:

  • 隔离性,ThreadLocal 为每个线程保存了变量副本,不同线程之间互相不影响,保证了数据在多线程环境中的隔离性。
  • 减少同步开销,上面演示的 SimpleDateFormat 案例。
  • 上下文的参数透传, 提高了代码清晰度,ThreadLocal 可以很方便的在一个请求中传递信息,无需显示的进行参数传递,上面演示的用户信息传递案例。

缺点:

  • 有内存泄漏的风险,使用完 ThreadLocal 后要及时清除 ThreadLocal 中的数据。
  • 不能作为长时间的存储介质,因为 ThreadLocal 的生命周期和当前线程一致,不适合做长期存储。
  • 增加资源开销,每个线程都要创建一个独立的变量,如果线程数很多,就会增加资源开销。
  • 不支持异步访问,在进行用户信息传递的时候,如果有异步线程,就会获取不到用户信息,不过这个也正常,因为 ThreadLocal 本身就是当前线程的,换了线程肯定是获取不到信息的。

如有错误的地方欢迎指出纠正。

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

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

相关文章

vue3:树的默认勾选和全选、取消全选

实现的功能&#xff0c;上面有个选择框&#xff0c;当选中全部时&#xff0c;下方树被全选 代码&#xff1a; <template><div><el-select v-model"selectAll" style"margin-bottom: 10px;" change"handleSelectAllChange">&…

electron打包dist为可执行程序后记【electron-quick-start】

文章目录 目录 文章目录 前言 一、直接看效果 二、实现步骤 1.准备dist文件夹 2.NVM管理node版本 3.准备electron容器并npm run start 4.封装成可执行程序 1.手动下载electron对应版本的zip文件&#xff0c;解决打包缓慢问题 2.安装packager 3.配置打包命令执行内容…

嵌入式linux中利用QT控制蜂鸣器方法

大家好,今天给大家分享一下,如何控制开发板上的蜂鸣器。 第一:开发板原理图 从原理图中可以得出,当引脚输出低电平的时候,对应的蜂鸣器发出响声。 第二:QT代码详细实现 设置一个按钮,点击即可控制BEEP状态发生反转。 #ifndef MAINWINDOW_H #define MAINWINDOW_H#in…

华为鸿蒙生态,威力估计被很多人低估了……

华为鸿蒙生态&#xff0c;威力估计被很多人低估了&#xff01;华为的鸿蒙千帆计划快成了&#xff0c;微信的加盟让计划就基本没问题了。 最近华为公布原生鸿蒙APP进度&#xff0c;在TOP5000应用里面&#xff0c;已经有4000支持了&#xff0c;不是已经开发完成&#xff0c;就是…

c++ 继承和组件的区别

一、什么是继承(Inheritance) 继承是面向对象编程的四大基本特性之一&#xff0c;它允许一个类&#xff08;派生类或子类&#xff09;继承另一个类&#xff08;基类或父类&#xff09;的属性和方法。通过继承&#xff0c;子类可以重用父类的代码&#xff0c;并且可以添加或覆盖…

# C++之STL整理(9)之list用法(创建、赋值、增删查改)详解

C之STL整理&#xff08;9&#xff09;之list用法&#xff08;创建、赋值、增删查改&#xff09;详解 注&#xff1a;整理一些突然学到的C知识&#xff0c;随时mark一下 例如&#xff1a;忘记的关键字用法&#xff0c;新关键字&#xff0c;新数据结构 C 的list用法整理 C之STL整…

win 安装comfigUI 和一些问题

安装内容&#xff0c;参考&#xff08;1&#xff09;有很详细安装方式 windows压缩包安装ComfyUI 在发布页面上&#xff0c;有一个适用于 Windows 的便携式单机版&#xff0c;可以在 Nvidia GPU 上运行&#xff0c;也可以只在 CPU 上运行。 官网下载地址&#xff1a; https:/…

Electron+Vue3整合 - 开发时状态整合

说明 本文介绍一下 Electron Vue3 的整合的基本操作。实现的效果是 &#xff1a; 1、一个正常的Vue3项目&#xff1b; 2、整合加入 Electron 框架 &#xff1a;开发时 Electron 加载的是开发的vue项目&#xff1b;步骤一&#xff1a;创建vue3项目 常规操作&#xff0c;不再赘…

(C语言)fscanf与fprintf函数详解

目录 1 fprintf详解 1.1 向文件流中输入数据 1.2 向标准输入流写数据 2. fscanf函数详解 2.1 从文件中读取格式化数据 2.2 从标准输入流中读取格式化数据 1 fprintf详解 头文件&#xff1a;stdio.h 该函数和printf的参数特别像&#xff0c;只是多了一个参数stream&#…

scss 和css 的区别 scss变量和css变量的区别

scss 和 css 的区别 语法差异&#xff1a; CSS 使用大括号 {} 和分号 ; 来定义样式规则和属性。SCSS 使用了 Sass 的语法&#xff0c;它允许使用类似编程语言的结构&#xff0c;如变量、嵌套规则、混合&#xff08;mixins&#xff09;和继承等。 嵌套规则&#xff1a; 在 SCSS …

删除word中下划线的内容

当试卷的题目直接含答案&#xff0c;不利用我们刷题。这时如果能够把下划线的内容删掉&#xff0c;那么将有利于我们复习。 删除下划线内容的具体做法&#xff1a; ①按ctrl H ②点格式下面的字体 ③选择下划线线型中的_____ ④勾选使用通配符并在查找内容中输入"?&qu…

增强现实(AR)开发框架

增强现实&#xff08;AR&#xff09;开发框架为开发者提供了构建AR应用程序所需的基本工具和功能。它们通常包括3D引擎、场景图、输入系统、音频系统和网络功能。以下是一些流行的AR开发框架。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流…

【C语言】贪吃蛇项目(2)- 实现代码详解

文章目录 前言一、游戏开始界面设计首先 - 打印环境界面其次 - 游戏地图、蛇身及食物的设计1、地图2、蛇身设置及打印3、食物 二、游戏运行环节蛇的上下左右移动等功能蛇的移动 三、结束游戏代码 前言 在笔者的前一篇博客中详细记载了贪吃蛇项目所需的一些必备知识以及我们进行…

MySQL面试题 3

问题1&#xff1a;char、varchar的区别是什么&#xff1f; varchar是变长而char的长度是固定的。如果你的内容是固定大小的&#xff0c;你会得到更好的性能。 问题2: TRUNCATE和DELETE的区别是什么&#xff1f; DELETE命令从一个表中删除某一行&#xff0c;或多行&#xff0…

上位机图像处理和嵌入式模块部署(树莓派4b实现动态插件)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 和上位机一样&#xff0c;我们的智能硬件如果想应用到更多的场景&#xff0c;那么势必需要实现更多的算法。这些算法和算法之间最好是松散耦合的插…

存储过程的使用(二)

目录 带 OUT 参数的存储过程 输入一个编号&#xff0c;查询数据表 emp中是否有这个编号&#xff0c;如果有返回对应员工姓名&#xff0c;如果没有&#xff0c;则提示没有对应员工 使用 EXEC 命令或者 PRINT执行含有 OUT参数的存储过程 使用 PL/SQL 块编辑程序调用含有 OUT …

智慧公厕是如何诞生的?

在城市化进程中&#xff0c;公共卫生设施的建设一直是重要议题之一。而随着科技的不断发展&#xff0c;智慧公厕作为一种创新的解决方案&#xff0c;逐渐成为了现代城市管理的亮点。那么&#xff0c;智慧公厕是如何产生的呢&#xff1f; 一、城市化进程的推动 城市人口的增加和…

排序 “壹” 之插入排序

目录 ​编辑 一、排序的概念 1、排序&#xff1a; 2、稳定性&#xff1a; 3、内部排序&#xff1a; 4、外部排序&#xff1a; 二、排序的运用 三、插入排序算法实现 3.1 基本思想 3.2 直接插入排序 3.2.1 排序过程&#xff1a; 3.2.2 代码示例&#xff1a; 3.2.3…

基于通达信---做T专用算法

什么是做T? 股票做T是股票市场中常见的一种投资策略,也就是股票进行T+0操作,通过当天买进的股票,在当天卖出,是股市中常见的一种超短线的操作。其中T就是指交易日,利用交易日中的股票涨跌来赚取差价。股票做T常见的类型就是正T和倒T。 1、正T 股票做正t就是指先买后卖,…

【Java框架】Spring框架(一)——Spring基本核心(IOC/DI)

目录 Java企业级框架企业级系统EJB概念解析EJB与Spring的恩怨情仇 Spring系统架构1. Data Access/Integration&#xff08;数据访问&#xff0f;集成&#xff09;2. Web 模块3. Core Container&#xff08;Spring 的核心容器&#xff09;4. AOP、Aspects、Instrumentation 和 M…