ThreadLocal(线程本地存储)
什么是 ThreadLocal?
ThreadLocal 是 Java 中用于实现线程本地存储的一个类。它的主要作用是为每个线程提供独立的变量副本,从而避免多线程环境下的数据共享和竞争问题。
ThreadLocal是一个工具类,允许你为每个线程创建独立的变量副本。- 每个线程访问
ThreadLocal变量时,都会获取到属于该线程的独立副本,其他线程无法访问或修改这个副本。 - 它的核心思想是:线程隔离。
使用场景
ThreadLocal 通常用于以下场景:
- 避免线程安全问题:
- 在多线程环境下,某些变量需要在线程间隔离,避免共享导致的竞争条件。
- 保存线程上下文信息:
- 比如在 Web 应用中,使用
ThreadLocal保存用户请求的上下文(如用户 ID、事务信息等)。
- 比如在 Web 应用中,使用
- 替代参数传递:
- 当某些方法需要频繁传递同一个对象时,可以用
ThreadLocal简化代码逻辑。
- 当某些方法需要频繁传递同一个对象时,可以用
工作原理
ThreadLocal 的核心机制如下:
- 每个线程都有一个
ThreadLocalMap,用于存储该线程的所有ThreadLocal变量。 - 当线程访问
ThreadLocal的get()方法时,会从当前线程的ThreadLocalMap中查找对应的值。 - 如果没有找到值,则调用
initialValue()方法初始化一个新值,并将其存储到ThreadLocalMap中。
基本用法
示例 1:基本使用
public class ThreadLocalExample {// 创建一个 ThreadLocal 变量private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);public static void main(String[] args) {Runnable task = () -> {// 获取当前线程的变量副本Integer value = threadLocal.get();System.out.println(Thread.currentThread().getName() + " 初始值: " + value);// 修改变量值threadLocal.set(value + 1);System.out.println(Thread.currentThread().getName() + " 修改后值: " + threadLocal.get());};// 创建多个线程Thread t1 = new Thread(task, "线程 1");Thread t2 = new Thread(task, "线程 2");t1.start();t2.start();}
}
输出结果:
线程 1 初始值: 0
线程 2 初始值: 0
线程 1 修改后值: 1
线程 2 修改后值: 1
说明:
- 每个线程都有独立的变量副本,互不干扰。
- 即使两个线程操作同一个
ThreadLocal对象,它们的值也是隔离的。
示例 2:在线程池中的使用
由于线程池中的线程会被复用,使用 ThreadLocal 时需要注意清理变量,否则可能导致内存泄漏。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadLocalWithThreadPool {private static ThreadLocal<String> threadLocal = new ThreadLocal<>();public static void main(String[] args) {ExecutorService pool = Executors.newFixedThreadPool(2);for (int i = 0; i < 5; i++) {int taskId = i;pool.submit(() -> {try {// 设置线程本地变量threadLocal.set("任务 " + taskId);System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());} finally {// 清理线程本地变量threadLocal.remove();}});}pool.shutdown();}
}
输出结果(顺序可能不同):
pool-1-thread-1: 任务 0
pool-1-thread-2: 任务 1
pool-1-thread-1: 任务 2
pool-1-thread-2: 任务 3
pool-1-thread-1: 任务 4
注意:
- 必须在任务结束时调用
threadLocal.remove()清理变量,避免线程复用时出现脏数据。
ThreadLocal 的优缺点
优点
- 线程隔离:
- 每个线程都有独立的变量副本,避免了多线程间的竞争和同步问题。
- 简化代码:
- 不需要通过参数传递共享变量,减少了代码复杂度。
缺点
- 内存泄漏风险:
- 如果不及时清理
ThreadLocal变量,可能会导致内存泄漏,尤其是在使用线程池时。
- 如果不及时清理
- 不适合所有场景:
ThreadLocal适用于线程隔离的场景,但不适合需要线程间共享数据的场景。
内存泄漏问题
ThreadLocal 的内存泄漏问题主要源于以下原因:
- 强引用链:
Thread→ThreadLocalMap→Entry(键为ThreadLocal引用,值为变量副本)。
- 未清理的变量:
- 如果
ThreadLocal对象被回收,但ThreadLocalMap中的Entry仍然持有对值的强引用,可能导致内存泄漏。
- 如果
解决方案:
- 在使用完
ThreadLocal后,务必调用remove()方法清理变量。
总结
| 特性 | 描述 |
|---|---|
| 用途 | 为每个线程提供独立的变量副本,避免线程间共享数据的问题。 |
| 优点 | 线程隔离、简化代码逻辑。 |
| 缺点 | 存在内存泄漏风险,必须手动清理变量。 |
| 典型场景 | 用户请求上下文、数据库连接管理、事务管理等需要线程隔离的场景。 |
实际开发中的主要应用场景
1. 用户请求上下文(Web 应用)
在 Web 应用中,每个用户的请求通常由一个独立的线程处理。为了在整个请求生命周期中保持用户相关的上下文信息(如用户 ID、事务信息等),可以使用 ThreadLocal。
示例场景:
- 保存用户登录信息:
每个线程处理一个用户的请求时,可以将用户的登录信息存储在ThreadLocal中,避免在方法间频繁传递参数。
public class UserContext {private static ThreadLocal<String> currentUser = new ThreadLocal<>();public static void setCurrentUser(String username) {currentUser.set(username);}public static String getCurrentUser() {return currentUser.get();}public static void clear() {currentUser.remove();}
}// 使用示例
public class RequestHandler {public void handleRequest() {try {// 设置当前用户UserContext.setCurrentUser("Alice");System.out.println("当前用户: " + UserContext.getCurrentUser());// 处理业务逻辑...} finally {// 清理上下文UserContext.clear();}}
}
2. 数据库连接管理
在多线程环境下,数据库连接通常是有限的资源。为了避免多个线程共享同一个数据库连接,可以为每个线程分配独立的连接,并通过 ThreadLocal 管理。
示例场景:
- 每个线程独享一个数据库连接:
使用ThreadLocal存储线程专属的数据库连接对象,确保线程安全。
import java.sql.Connection;
import java.sql.DriverManager;public class ConnectionManager {private static ThreadLocal<Connection> threadLocalConnection = ThreadLocal.withInitial(() -> {try {return DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password");} catch (Exception e) {throw new RuntimeException("获取数据库连接失败", e);}});public static Connection getConnection() {return threadLocalConnection.get();}public static void closeConnection() {Connection connection = threadLocalConnection.get();if (connection != null) {try {connection.close();} catch (Exception e) {e.printStackTrace();}}threadLocalConnection.remove(); // 清理连接}
}// 使用示例
public class DatabaseService {public void executeQuery() {Connection conn = ConnectionManager.getConnection();try {// 执行 SQL 查询...} finally {ConnectionManager.closeConnection();}}
}
3. 事务管理
在分布式系统或复杂业务逻辑中,事务管理需要贯穿整个方法调用链。通过 ThreadLocal,可以在线程范围内维护事务状态,确保事务的一致性。
示例场景:
- 事务上下文管理:
在一个事务中,所有方法都可以访问同一个事务上下文,而无需显式传递事务对象。
public class TransactionContext {private static ThreadLocal<Boolean> inTransaction = ThreadLocal.withInitial(() -> false);public static void beginTransaction() {inTransaction.set(true);System.out.println("事务已开启");}public static boolean isInTransaction() {return inTransaction.get();}public static void commit() {if (isInTransaction()) {System.out.println("事务已提交");inTransaction.remove();}}public static void rollback() {if (isInTransaction()) {System.out.println("事务已回滚");inTransaction.remove();}}
}// 使用示例
public class TransactionService {public void performTransaction() {TransactionContext.beginTransaction();try {// 执行业务逻辑...System.out.println("执行事务操作");TransactionContext.commit();} catch (Exception e) {TransactionContext.rollback();}}
}
4. 避免线程安全问题
在某些情况下,类中的变量可能需要被多个方法访问,但又不希望这些变量被多个线程共享。可以通过 ThreadLocal 实现线程隔离。
示例场景:
- SimpleDateFormat 的线程安全问题:
SimpleDateFormat是非线程安全的,但如果每个线程都有自己的SimpleDateFormat实例,就可以避免线程安全问题。
import java.text.SimpleDateFormat;
import java.util.Date;public class DateFormatUtil {private static ThreadLocal<SimpleDateFormat> threadLocalDateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));public static String formatDate(Date date) {return threadLocalDateFormat.get().format(date);}
}// 使用示例
public class DateFormatExample {public static void main(String[] args) {Date now = new Date();System.out.println(DateFormatUtil.formatDate(now));}
}
5. 日志追踪
在分布式系统中,为了追踪某个请求的完整调用链路,可以使用 ThreadLocal 存储唯一的请求 ID(Trace ID)。这样,在整个请求处理过程中,所有的日志都会带上这个 Trace ID。
示例场景:
- 日志上下文管理:
在每个线程中存储唯一的 Trace ID,用于日志记录。
public class LogContext {private static ThreadLocal<String> traceId = new ThreadLocal<>();public static void setTraceId(String id) {traceId.set(id);}public static String getTraceId() {return traceId.get();}public static void clear() {traceId.remove();}
}// 使用示例
public class Logger {public static void log(String message) {String traceId = LogContext.getTraceId();System.out.println("[" + traceId + "] " + message);}
}// 请求处理
public class RequestHandler {public void handleRequest() {try {LogContext.setTraceId("TRACE-12345");Logger.log("开始处理请求");// 处理业务逻辑...Logger.log("请求处理完成");} finally {LogContext.clear();}}
}
总结
ThreadLocal 的主要应用场景包括:
- 用户请求上下文:存储用户会话信息。
- 数据库连接管理:为每个线程分配独立的数据库连接。
- 事务管理:维护事务上下文。
- 避免线程安全问题:为每个线程提供独立的对象实例。
- 日志追踪:为每个请求生成唯一的 Trace ID。
相关文章:
ThreadLocal(线程本地存储)
什么是 ThreadLocal? ThreadLocal 是 Java 中用于实现线程本地存储的一个类。它的主要作用是为每个线程提供独立的变量副本,从而避免多线程环境下的数据共享和竞争问题。 ThreadLocal 是一个工具类,允许你为每个线程创建独立的变量副本。每…...
《Python实战进阶》No24: PyAutoGUI 实现桌面自动化
No24: PyAutoGUI 实现桌面自动化 摘要 PyAutoGUI 是一个跨平台的桌面自动化工具,能够模拟鼠标点击、键盘输入、屏幕截图与图像识别,适用于重复性桌面任务(如表单填写、游戏操作、批量文件处理)。本集通过代码截图输出日志的实战形…...
功耗电流和耗电量的获取
1. 实验室环境: 在受控的实验条件下,我们使用 PowerMonitor 精确控制变量(如固定设备型号和系统版本、清理后台应用、设置恒定的亮度与音量、确保稳定的网络连接等),以获取高精度的电流测量数据,从而准确评…...
医疗送药机器人“空间拓扑优化+动态算法决策+多级容错控制”三重链式编程技术解析与应用
一、引言 1.1 研究背景与意义 在医疗体系中,高效精准的药品配送是保障医疗服务质量和患者安全的关键环节。随着医疗技术的不断进步和医疗需求的日益增长,传统的人工送药方式逐渐暴露出诸多弊端,如配送效率低下、易受人为因素干扰导致错误率上升、人力成本高昂等。特别是在…...
C++【类和对象】(结束篇)
C类和对象 1.static成员2.友元3.内部类4.匿名对象5.对象拷贝时的编译器优化 1.static成员 用static修饰的成员变量叫做静态成员变量,静态成员一定要在类外进行初始化。静态成员变量为所有类的共享,放入静态区,不属于某个具体对象,…...
[CISCN 2022 初赛]ezpop(没成功复现)
打开在线环境可以看到: 记得之前做过一个类似的就是有点像照着漏洞去复现。应该可以直接在网上找到链子去打。 www.zip查看路由是 Index/test,然后 post 传参 a: exp(参考了别的大神的wp): <?php //…...
QT编程之QGIS
一、QGIS介绍 Quantum GIS(QGIS)是开源地理信息系统桌面软件,使用GNU(General Public License)授权, 属于 Open Source eospatial Foundation( OSGeo )的官方计划。在 GNU 授权下&am…...
福特售后再添亮点,为烈马模块化车身改装提供专业支持
2024年5月17日,中国上海 2024 年北京国际车展期间,纯血敞篷越野车国产福特烈马正式上市并公布全系厂商指导价,价格区间为29.98万元至43.88万元。作为一台风格鲜明,个性突出的纯血硬核越野车,诞生于1966年的福特烈马&a…...
嵌入式C语言中堆栈管理与数据存储的精髓
在嵌入式开发中,理解C语言的内存管理和数据存储机制是至关重要的。本文将从堆栈管理和数据存储两个方面,深入探讨C语言在嵌入式Linux开发中的应用。 一、堆栈管理 1.1 栈的初始化与作用 栈是C语言运行的基础,主要用于存储函数参数、局部变量、函数返回值和编译器生成的临时…...
003_快乐数
链接:202. 快乐数 - 力扣(LeetCode) 202.快乐数 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为: 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为…...
【MySQL数据库】约束
在MySQL数据库中,约束(Constraint)是用于限制表中数据的一种规则,目的是为了确保数据的完整性以及一致性。下面我们就从建表时的约束、建表后如何添加约束等几个方面,讲解MySQL中常用的几种约束。 创建时约束 分类 非空约束 非空…...
SANS 网络安全 网络安全三件套
基本设置篇 一、在线安全的四个误解 Internet实际上是个有来有往的世界,你可以很轻松地连接到你喜爱的站点,而其他人,例如黑客也很方便地连接到你的机器。实际上,很多机器都因为自己很糟糕的在线安全设置无意间在…...
LSTM方法实践——基于LSTM的汽车销量时序建模与预测分析
Hi,大家好,我是半亩花海。本实验基于汽车销量时序数据,使用LSTM网络(长短期记忆网络)构建时间序列预测模型。通过数据预处理、模型训练与评估等完整流程,验证LSTM在短期时序预测中的有效性。 目录 一、实验…...
[Windows] 轻量级景好鼠标录制器 v2.1 单文件版,支持轨迹+鼠标键盘录制复刻
[Windows] 轻量级景好鼠标录制器 链接:https://pan.xunlei.com/s/VOLHz0rPyqdhV4bgyTYuW6W7A1?pwd98uj# 软件特性: 高效播放控制:动作间隔优化至100 ms,进度条可视化,支持随机循环/多次播放。 深度自定义࿱…...
表单 schema 配置化
一、前沿 基于 Ant Design Vue 组件库实现了表单的配置化生成,通过 schema 配置化的方式实现表单的动态渲染、数据绑定和更新等功能,而提交按钮及获取数据逻辑由使用方自行提供。通过 schema 对象来定义表单的结构和属性,modelData 对象存储…...
LINUX --- KVM
什么是 KVM?– 基于内核的虚拟机简介 – AWS (amazon.com) 什么是 KVM? 基于内核的虚拟机(KVM)是一种软件功能,您可以将其安装在物理 Linux 机器上以创建虚拟机。虚拟机是一种软件应用程序,可作为另一台实…...
LabVIEW VI Scripting实现连接器窗格自动化
通过VI Scripting自动化配置连接器窗格,可大幅提升开发效率、统一接口规范,并适配动态需求。以下为真实场景中的典型应用案例,涵盖工业、汽车电子及教育领域,展示其实际价值与实施效果。 特点: 程序化配置:…...
网络安全信息收集[web子目录]:dirsearch子目录爆破全攻略以及爆破字典结合
目录 一、dirsearch 工具详细使用攻略 1. 安装 前提条件 安装步骤 可选:直接下载预编译版本 2. 基本用法 命令格式 参数说明 示例 3. 核心功能与高级用法 3.1 多线程加速 3.2 自定义字典 3.3 递归扫描 3.4 过滤响应 3.5 添加请求头 3.6 代理支持 3…...
【Msq8.0无需登陆进行重置密码】
【Msq8.0无需登陆进行重置密码】 Mysql的正常启动设置Msql的无密码登陆前操作 Mysql的正常启动 我使用的是Mac电脑,电脑上的Mysql8.0是处于运行状态,如果关闭状态也可以。 设置Msql的无密码登陆前操作 我们对Markdown编辑器进行了一些功能拓展与语法支…...
pandas表格内容比较
前阵子来了一个211大学实习生(小男生),要比较2个版本字段的变化,辅助完成系统升级字段替换,要求找出哪些字段是新增的,哪些字段是删除的,哪些字段是属性信息修改的,要求半天时间搞定…...
TMS320F28P550SJ9学习笔记13: 软件I2C_驱动AT24Cxx存储芯片
今日尝试配置软件I2C通信,我的目标通信芯片是AT24C64,相较于AT24C02这样的8位寻址,它是16位寻址的,所以有些不同 文章提供测试代码讲解、完整工程下载、测试效果图 目录 软件I2C引脚初始化: C内联函数改变SCL与SDA的输…...
Vuex 基础概念与环境搭建
Vuex 是实现数据集中式状态管理的插件。所有组件共享 Vuex 中的数据,当任意组件修改数据时,其他组件会同步更新。与全局事件总线的区别在于: 全局事件总线:数据传递但未真正共享Vuex:数据存储在中央仓库,实…...
手抖预防方法主要包括以下几个方面
手抖预防方法主要包括以下几个方面: 1. 心理调节:保持心情舒畅,避免过度紧张和焦虑。如有必要,可以寻求心理医生帮助进行心理调适。 2. 充分休息:保证充足的睡眠时间,避免熬夜和过度劳累。合理安排工作和…...
使用libwebsocket写一个server
lws-minimal-ws-server这些例程在buildroot里面,更新的话只能整体编译,十分麻烦和耗时,在sdk外面建立项目,单独开发,会更合适。 创建程序文件夹和文件 mkdir ./ws_cam cd ./ws_cam cp ../luckfox-pico/sysdrv/source…...
SpringBoot MCP 入门使用
随着AI的火爆,最近发现MCP在未来确实大有可为,作为一名javaer怎么可以落后在历史洪流呢,根据官网和cursor也从零开始体验一下自定义mcp server。以后可以根据自己业务场景做出各种适合自身业务的工具。 至于什么是MCP 可以到https://modelcon…...
Windows 11 安装Docker Desktop环境
1、确认CPU开启虚拟化 打开任务管理器,切换到“性能”选项卡,查看 CPU 信息。若“虚拟化”状态显示为“已启用”,则表示虚拟化已开启;若显示为“已禁用”,则需要在启动时进入 BIOS 开启虚拟化设置(若显示已…...
C++ STL算法函数 —— 应用及其操作实现
一、STL算法函数分类概述 STL算法库提供了大量实用函数,按功能可分为以下五类: 1. 不修改序列的操作 定义:这些算法不会改变容器中的元素,仅对数据进行查询或统计。 典型函数: 函数功能示例find(first, last, value…...
汽车保养记录用什么软件记录,汽车维修记录查询系统,佳易王汽车保养维护服务记录查询管理系统操作教程
一、概述 本实例以佳易王汽车保养维护服务记录查询管理系统为例说明,其他版本可参考本实例。试用版软件资源可到文章最后了解,下载的文件为压缩包文件,请使用免费版的解压工具解压即可试用。 软件特点:1、功能实用,操…...
深入理解C/C++堆数据结构:从原理到实战
一、堆的本质与特性 1.1 什么是堆数据结构? 堆(Heap)是一种特殊的完全二叉树,它满足以下核心性质: 堆序性:每个节点的值都满足特定顺序关系 结构性:完全二叉树的结构特性(除最后一…...
WebRTC中音视频服务质量QoS之RTT衡量网络往返时延的加权平均RTT计算机制详解
WebRTC中音视频服务质量QoS之RTT衡量网络往返时延加权平均RTT计算机制的详解 WebRTC中音视频服务质量QoS之RTT衡量网络往返时延加权平均RTT计算机制的详解 WebRTC中音视频服务质量QoS之RTT衡量网络往返时延加权平均RTT计算机制的详解前言一、 RTT 网络往返时延的原理1、…...
