ThreadLocal的使用介绍和底层原理解析和开源框架的使用实例
文章目录
- ThreadLocal的使用介绍和底层原理解析和开源框架的使用实例
- ThreadLocal简介
- ThreadLocal使用示例
- ThreadLocal原理解析
- Spring中ThreadLocal的应用
- 小结
- ThreadLocal的使用步骤
- 常见面试题
- 案例解析(框架源码经典案例)
- 案例实战
ThreadLocal的使用介绍和底层原理解析和开源框架的使用实例
ThreadLocal简介
ThreadLocal是一个线程内部的数据存储类,它可以为每个线程提供独立的变量副本,不同线程间的变量无法相互访问和修改。这避免了每个线程都要维护一套独立变量的麻烦,并且也减少了线程之间不必要的数据争用。ThreadLocal适用于这样的场景:每个线程需要有自己单独的实例,而不是共享实例。例如,在 web 应用中,每个请求被一个新的线程处理,每个线程需要有自己的变量实例。
ThreadLocal使用示例
public class ThreadLocalExample {// 线程局部变量,每个线程有自己的变量副本private ThreadLocal<String> threadLocal = new ThreadLocal<>();public void set(String value) {threadLocal.set(value);}public String get() {return threadLocal.get();}
}public class ThreadLocalTest {public static void main(String[] args) {ThreadLocalExample example = new ThreadLocalExample();// 线程1设置threadLocal变量example.set("Thread1 local variable"); System.out.println("Thread1 get: " + example.get());// 线程2无法获取线程1设置的threadLocal变量Thread thread2 = new Thread() {public void run() {example.set("Thread2 local variable"); System.out.println("Thread2 get: " + example.get());}};thread2.start();}
}
运行结果:
Thread1 get: Thread1 local variable
Thread2 get: Thread2 local variable每个线程获取自己设置的值,并不同线程间互不干扰。
ThreadLocal原理解析
ThreadLocal内部使用ThreadLocalMap来存储每个线程的变量副本。ThreadLocalMap是ThreadLocal的静态内部类,每个线程都有自己的ThreadLocalMap副本。
ThreadLocal中get()方法的实现如下:
public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();
}
- 获取当前线程对象
- 获取当前线程的ThreadLocalMap(实际上是从当前线程的ThreadLocalMap变量中获取)
- 在ThreadLocalMap中获取当前ThreadLocal变量对应的value值
- 如果不存在,调用setInitialValue()方法初始化value值,并存储到ThreadLocalMap中
这样,每个线程的ThreadLocal变量都被存储在自己的ThreadLocalMap中,相互独立,互不干扰。
ThreadLocalMap使用ThreadLocal对象作为key来存储value值。当ThreadLocal对象被回收时,由弱引用产生的key会在下一次GC时被清除,这会导致value值无法被访问到,出现内存泄漏,所以我们应该手动调用remove()方法,在ThreadLocal不再使用时清除它。
Spring中ThreadLocal的应用
Spring框架中大量使用了ThreadLocal,例如:
- TransactionSynchronizationManager: 管理线程事务上下文信息。
- RequestContextHolder: 存储request上下文,用于获取request信息。
- LocaleContextHolder: 存储locale上下文,用于获取locale信息。
这些类都使用ThreadLocal来为每个线程提供单独变量副本,避免了线程间数据交叉和覆盖的问题。
@Component
public class RequestHolder {private ThreadLocal<HttpServletRequest> requestHolder = new ThreadLocal<>();public void setRequest(HttpServletRequest request) {requestHolder.set(request);}public HttpServletRequest getRequest() {return requestHolder.get(); }
}
这样每个线程在处理request时可以调用setRequest()方法存储自己的request对象,在其他地方需要获取request信息时,调用getRequest()方法即可获取当前线程对应的request对象。这就避免了每个线程都要维护一个request对象的麻烦,也减少了线程之间request对象混淆的问题。
小结
ThreadLocal为每个线程提供独立的变量副本,实现了线程隔离。它的主要作用是为每个线程保存一些 thread-local 的上下文信息,这些信息在线程的生命周期内起作用。
它的内部原理是使用ThreadLocalMap来存储每个线程对应的变量副本,键值为ThreadLocal对象,值则为变量副本。
它应用在许多地方,如Spring框架等,用于避免线程间数据交叉和覆盖的问题。
但是它也有一定的弊端,由于ThreadLocalMap使用ThreadLocal作为key,如果ThreadLocal被回收,就可能出现内存泄漏的问题。所以应该手动调用ThreadLocal的remove()方法,在ThreadLocal不再使用时清除它。
ThreadLocal的使用步骤
- 定义ThreadLocal变量:
private ThreadLocal<String> threadLocal = new ThreadLocal<>();
- 在每个线程内设置ThreadLocal变量:
threadLocal.set("value");
- 获取ThreadLocal变量:
String value = threadLocal.get();
- 删除ThreadLocal变量:
threadLocal.remove();
- 目前ThreadLocal类提供的方法有:
- set(T value): 设置当前线程的thread local变量的值。
- get(): 获取当前线程的thread local变量的值。
- remove(): 删除当前线程的thread local变量的值。
- initialValue(): 返回当前线程第一次调用get()时的值,后续调用get()会直接返回这个值。
- ThreadLocal应用举例:
- 解决数据库连接共享问题:每个线程都有自己的数据库连接,避免线程之间的连接混用。
private ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();public Connection getConnection() {Connection conn = connectionHolder.get();if (conn == null) {conn = DriverManager.getConnection("jdbc:mysql://localhost/test", "root", "root");connectionHolder.set(conn);}return conn;
}
- 解决Session共享问题:每个线程都有自己的Session实例。
private ThreadLocal<HttpSession> sessionHolder = new ThreadLocal<>();public HttpSession getSession() {HttpSession session = sessionHolder.get();if (session == null) {session = request.getSession(); sessionHolder.set(session);}return session;
}
- ThreadLocal内存泄漏问题的解决:
由于ThreadLocalMap使用ThreadLocal作为key来存储entry,如果ThreadLocal被回收,key变成null,就会出现内存泄漏。所以ThreadLocal使用完毕后,需要调用remove()方法清除数据,避免出现内存泄漏。
threadLocal.remove();
常见面试题
- ThreadLocal能否解决线程安全问题?
答:ThreadLocal不能解决线程安全问题。ThreadLocal为每个线程提供独立的变量副本,实现线程隔离,但并不保证线程安全。如果多个线程同时修改同一个ThreadLocal变量,还是需要额外的同步措施保证线程安全。
- ThreadLocal会引起内存泄漏么?如何避免?
答:ThreadLocal会引起内存泄漏。因为ThreadLocalMap使用ThreadLocal作为key来存储entry,如果ThreadLocal被回收,key变成null,就会出现内存泄漏。
解决方法是在ThreadLocal不再使用时,手动调用remove()方法清除数据,避免出现内存泄漏。
- ThreadLocal的value为什么推荐使用引用类型?
答:因为每个线程访问自己的副本变量,如果使用基本类型,ThreadLocal需要为每个线程创建一个变量副本,这会消耗较多内存。
而如果使用引用类型,每个线程访问的都是同一个引用对象的副本,只是每个线程可以对这个对象进行修改,这可以节省内存,所以推荐ThreadLocal的value使用引用类型。
- ThreadLocalMap的工作原理是什么?
答:ThreadLocalMap是ThreadLocal的静态内部类,每个线程都有自己的ThreadLocalMap副本。
ThreadLocalMap使用ThreadLocal对象作为key来存储value值。在调用ThreadLocal的get()方法时,会先得到当前线程的ThreadLocalMap,然后再从其中获取与当前ThreadLocal对象关联的值。
put方法会将ThreadLocal对象作为key放入map中,并关联一个value。
当ThreadLocal对象被回收时,由弱引用产生的key会在下一次GC时被清除,这会导致value值无法被访问到,出现内存泄漏,所以在ThreadLocal不再使用时需要手动调用remove()方法清除数据。
- 个人理解ThreadLocal的主要作用和应用场景?
答:ThreadLocal的主要作用是为每个线程提供独立的变量副本,实现线程隔离。
它的应用场景主要有:
- 为每个线程绑定请求相关数据,避免同一个请求被不同线程处理时出现数据混淆的问题。
- 为每个线程单独绑定数据库连接、Session等资源,避免线程间共享资源。
- 解决变量共享导致的线程安全问题,通过给每个线程独立变量副本来隔离线程。
案例解析(框架源码经典案例)
这里我们以Spring中的ThreadLocal应用举个例子加深理解。
Spring中TransactionSynchronizationManager使用ThreadLocal来管理事务上下文信息。它定义了两个ThreadLocal变量:
private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<Map<Object, Object>>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations");
- resources: 用于存储事务相关资源,如数据库连接、Session等。
- synchronizations: 用于存储事务同步对象,如事务完成后需要执行的回调等。
当开始一个事务时,通过TransactionSynchronizationManager进行事务上下文的存储:
StaticTransactionSynchronizationAdapter.registerSynchronization(new TransactionSynchronizationAdapter() {@Overridepublic void afterCompletion(int status) {if (status == STATUS_COMMITTED) {afterCommit();}else {afterRollback(); }}}
);
TransactionSynchronizationManager.bindResource(ds, conn);
- 向synchronizations ThreadLocal集合中添加一个事务同步对象,用于在事务完成后执行回调。
- 向resources ThreadLocal集合中添加连接资源,以方便事务管理器管理。
事务完成时,TransactionSynchronizationManager会执行:
TransactionSynchronizationManager.initSynchronization();
try {// 执行回调TransactionSynchronizationManager.cleanupSynchronization();
}
finally {// 清除ThreadLocal上的变量TransactionSynchronizationManager.clear();
}
- 调用initSynchronization执行注册的同步回调。
- 调用cleanupSynchronization执行后续清理工作。
- 调用clear()方法清除ThreadLocal上的事务上下文,避免内存泄漏。
这样,通过ThreadLocal为每个事务线程独立存储事务上下文,避免了线程间数据混淆和干扰的问题。同时也在事务完成后手动调用clear()方法清除ThreadLocal,解决了内存泄漏的问题。
这就是ThreadLocal在Spring事务管理中的典型应用, hope这能加深您对ThreadLocal用法的理解。
案例实战
这里我们来实现一个简单的Session管理,使用ThreadLocal为每个线程单独绑定Session实例。
public class SessionManager {private ThreadLocal<HttpSession> sessionHolder = new ThreadLocal<>();public HttpSession getSession() {HttpSession session = sessionHolder.get();if (session == null) {session = request.getSession(); sessionHolder.set(session);}return session;}public void clear() {sessionHolder.remove();}
}
- 定义ThreadLocal变量sessionHolder来存储每个线程的HttpSession实例。
- getSession()方法先从sessionHolder中获取 SESSION,如果不存在则创建一个新的SESSION,并存储到sessionHolder中。
- clear()方法用于手动清除sessionHolder,避免内存泄漏。
使用方式:
// 获取Session
SessionManager manager = new SessionManager();
HttpSession session = manager.getSession();// 使用Session
session.setAttribute("key", "value");// 获取Attribute
String value = (String) session.getAttribute("key");// 手动清除
manager.clear();
使用ThreadLocal为每个线程单独存储SESSION,避免了线程间SESSION实例的混淆,也能很好地管理SESSION生命周期。
同时也演示了如何防止ThreadLocal内存泄漏的问题,手动调用clear()方法清除ThreadLocal变量。此案例结合理论介绍了ThreadLocal的整个使用过程,包括定义ThreadLocal变量,为每个线程单独设置变量值,获取变量值,清除ThreadLocal变量等步骤。并分析了其工作原理和应用场景,希望能够帮助大家进一步理解和熟练掌握ThreadLocal。
相关文章:
ThreadLocal的使用介绍和底层原理解析和开源框架的使用实例
文章目录 ThreadLocal的使用介绍和底层原理解析和开源框架的使用实例ThreadLocal简介ThreadLocal使用示例ThreadLocal原理解析Spring中ThreadLocal的应用小结ThreadLocal的使用步骤常见面试题案例解析(框架源码经典案例)案例实战 ThreadLocal的使用介绍和底层原理解析和开源框架…...

带你学c带你飞-P7取值范围
比特位 CPU能读懂的最小单元——比特位,bit,b 字节 内存机构的最小寻址单元——字节,Byte,B 1Byte8bit 进制 怎么算 注意:int默认是signed类型,signed类型第一位是符号位 符号位 存放signed类型的存…...
ramfs, rootfsinitramfs
什么是ramfs? ramfs是一个非常简单的文件系统,它将Linux的磁盘缓存机制(页面缓存和dentry缓存)导出为一个动态可调整大小的基于ram的文件系统。 Linux通常将所有文件缓存在内存中。从后备存储(通常是挂载文件系统的块设备)读取的数据页被保留下来,以防…...
十三届蓝桥杯研究生组国赛-最大公约数(线段树+二分)
十三届蓝桥杯研究生组国赛-最大公约数 1、问题描述2、解题思路2.1 解法一:暴力查询区间gcd(75%)2.2 解法二:线段树+二分法(AC)1、问题描述 问题描述 给定一个数组, 每次操作可以选择数组中任意两个相邻的元素 x , y x,y x,y...

数据结构——二叉树层序遍历
数据结构——二叉树层序遍历 107. 二叉树的层序遍历 II199. 二叉树的右视图思路: 637. 二叉树的层平均值 107. 二叉树的层序遍历 II 107. 二叉树的层序遍历 II 给你二叉树的根节点 root ,返回其节点值 自底向上的层序遍历 。 (即按从叶子节…...

【微机原理】8088/8086微处理器
目录 一、8088/8086的功能结构 1.总线接口部件(BIU) 2.执行部件(EU) 二、8088/8086的寄存器结构(14个) 溢出标志的概念 溢出和进位的区别 8086CPU是Intel系列的16位微处理器,他有16根数据…...
springboot第12集:DAO功能代码
在Spring Boot中,DAO是数据访问对象的缩写,它是一种设计模式用于提供对数据库操作的抽象层。通过使用DAO模式,我们可以将数据操作与业务逻辑分离,并提供一个单独的接口来执行所有的数据库操作。 在Spring Boot中,通常使…...

基于KZG多项式承诺方案的RLN
1. 引言 RLN——Rate-Limiting Nullifier为PSE团队主导的项目,源自: Barry White Hat 2019年博客 Semaphore RLN, rate limiting nullifier for spam prevention in anonymous p2p setting RLN(Rate-Limiting Nullifier)是一种…...

《站在巨人的肩膀上学习Java》
Java从诞生距今已经有28年了,在这段时间里,随着Java版本的不断迭代,Java新特性的不断出现,使得Java被使用的越来越广泛。在工程界Java语言一直是大家最喜欢的语言之一,Java一直排行在编程语言热门程度的前3名。 可想而…...

敏捷ACP.敏捷估计与规划.Mike Cohn.
第一部分 传统规划失败的原因 vs 敏捷规划有效的原因 传统的项目规划方式往往会让我们失望。要回答-一个 新产品的范围/进度/资源的组合问题,传统规划过程不一定会产生令人非常满意的答案和最终产品。以下- -些论据可以支持这个结论: ●大约2/3的项目会显著超…...

[创新工具和方法论]-01- DOE课程基础知识
文章目录 1.DOE实验设计的介绍1.1 什么是实验设计DOE?1.2 DOE的优势有哪些?1.3 如何开展DoE研究?步骤 2.DOE实验培训3.数据分析步骤4.实验的随机化5.偏差6.R方 相关系数假设检验 7.三因子二水平全因子设计 1.DOE实验设计的介绍 实验设计是一种安排实验和分析实验数…...

LeetCode-1033. 移动石子直到连续
题目链接 LeetCode-1033. 移动石子直到连续 题目描述 题解 题解一(Java) 作者:仲景 这题目挺难懂的,得画画图才能更好的理解 这也是LeetCode的尿性,习惯了,非得整这种别人看不懂的鸟语 你可以这样理解&a…...
JVM调优入门指南:掌握步骤、参数和场景
前言 作为Java开发者,我们经常需要优化应用的性能,其中JVM调优是非常重要的一部分。在本文中,我们将介绍JVM调优的一般步骤和方法,了解JVM调优参数,如堆大小、新生代比例、GC算法等参数的作用和配置方式,并…...
基于JSP+MySQL的跳蚤市场网站设计与开发
摘 要 在当今社会,网络信息已经不是什么很陌生的词汇,每天都在这个信息时代里生活着并且享受着它带来的与众不同。网络购物可以说是飞速发展的,这种购物方式逐渐的影响着人们的衣食住行。所以利用计算机实现 跳蚤市场网站设计与开发势在必行。本网站是一个校园的跳蚤市场网…...

内网穿透NPS和宝塔Nginx配合使用,开启SSL访问本地局域网网络
并非为了教学,仅供自己记录,方便下次用。所以内容不会刻意花时间写的很细节详细。 1. 服务器NPS配置 NPS install安装后,配置文件会在其他位置,通过是 /etc/nps/nps.conf目录。 找到进行修改,主要修改的是http_proxy_p…...

ToLua框架
ToLua 是一个用于在 Unity 中为 Lua 提供 C# 语言绑定的框架。通过 ToLua,你可以方便地将 C# 代码暴露给 Lua 脚本,并在 Lua 脚本中调用 C# 类、方法和属性。 更新流程 原理:使用AssetBundle进行资源的更新,而由于lua运行时才编…...

Golang-常见数据结构Map
Map map 是一种特殊的数据结构:一种元素对(pair)的无序集合,pair 的一个元素是 key,对应的另一个元素是 value,所以这个结构也称为关联数组或字典。这是一种快速寻找值的理想结构:给定 key&…...

基于空间矢量脉宽调制(SVPWM)的并网逆变器研究(Simulink)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...
介绍tcpdump在centos中的使用方法
tcpdump是一款强大的命令行数据包分析器,支持多种过滤和抓包参数。下面将介绍tcpdump的常用抓包参数。当需要监控CentOS系统的网络流量或者进行网络故障排查时,可以使用tcpdump来捕获数据包并进行分析。 下面介绍在CentOS中使用tcpdump的方法࿱…...

机器学习实战:Python基于DT决策树模型进行分类预测(六)
文章目录 1 前言1.1 决策树的介绍1.2 决策树的应用 2 Scikit-learn数据集演示2.1 导入函数2.2 导入数据2.3 建模2.4 评估模型2.5 可视化决策树2.6 优化模型2.7 可视化优化模型 3 讨论 1 前言 1.1 决策树的介绍 决策树(Decision Tree,DT)是一…...

【CSS position 属性】static、relative、fixed、absolute 、sticky详细介绍,多层嵌套定位示例
文章目录 ★ position 的五种类型及基本用法 ★ 一、position 属性概述 二、position 的五种类型详解(初学者版) 1. static(默认值) 2. relative(相对定位) 3. absolute(绝对定位) 4. fixed(固定定位) 5. sticky(粘性定位) 三、定位元素的层级关系(z-i…...

页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...

如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...
数据链路层的主要功能是什么
数据链路层(OSI模型第2层)的核心功能是在相邻网络节点(如交换机、主机)间提供可靠的数据帧传输服务,主要职责包括: 🔑 核心功能详解: 帧封装与解封装 封装: 将网络层下发…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)
引言 工欲善其事,必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后,我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集,就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...
LangFlow技术架构分析
🔧 LangFlow 的可视化技术栈 前端节点编辑器 底层框架:基于 (一个现代化的 React 节点绘图库) 功能: 拖拽式构建 LangGraph 状态机 实时连线定义节点依赖关系 可视化调试循环和分支逻辑 与 LangGraph 的深…...

CVPR2025重磅突破:AnomalyAny框架实现单样本生成逼真异常数据,破解视觉检测瓶颈!
本文介绍了一种名为AnomalyAny的创新框架,该方法利用Stable Diffusion的强大生成能力,仅需单个正常样本和文本描述,即可生成逼真且多样化的异常样本,有效解决了视觉异常检测中异常样本稀缺的难题,为工业质检、医疗影像…...

渗透实战PortSwigger靶场:lab13存储型DOM XSS详解
进来是需要留言的,先用做简单的 html 标签测试 发现面的</h1>不见了 数据包中找到了一个loadCommentsWithVulnerableEscapeHtml.js 他是把用户输入的<>进行 html 编码,输入的<>当成字符串处理回显到页面中,看来只是把用户输…...
上位机开发过程中的设计模式体会(1):工厂方法模式、单例模式和生成器模式
简介 在我的 QT/C 开发工作中,合理运用设计模式极大地提高了代码的可维护性和可扩展性。本文将分享我在实际项目中应用的三种创造型模式:工厂方法模式、单例模式和生成器模式。 1. 工厂模式 (Factory Pattern) 应用场景 在我的 QT 项目中曾经有一个需…...