当前位置: 首页 > article >正文

Java 中的 ThreadLocal 详解:从基础到源码

Java 中的 ThreadLocal 详解:从基础到源码

引言

在 Java 多线程编程中,ThreadLocal是一个经常被提及的概念。它提供了一种线程局部变量的机制,使得每个线程都可以独立地存储和访问自己的变量副本,而不会与其他线程产生冲突。本文将深入探讨ThreadLocal的核心概念、应用场景、源码实现及其潜在的内存泄漏问题,并结合具体代码样例进行分析。

一、ThreadLocal 概述

1.1 什么是 ThreadLocal?

ThreadLocal是 Java 中的一个类,位于java.lang包下。它为每个使用该变量的线程都提供一个独立的变量副本,每个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。

1.2 核心作用

  • 线程封闭:将变量限制在单个线程内,避免多线程竞争带来的同步问题。
  • 简化编程模型:在复杂的调用链中传递上下文信息,避免显式传递参数。
  • 提高性能:减少线程间共享变量的访问开销,避免锁竞争。

1.3 基本使用方法

public class ThreadLocalExample {// 创建ThreadLocal实例private static final ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);public static void main(String[] args) {// 创建两个线程Thread thread1 = new Thread(() -> {for (int i = 0; i < 5; i++) {// 获取当前线程的变量副本int value = threadLocal.get();// 修改副本值threadLocal.set(value + 1);System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());}// 移除当前线程的变量副本threadLocal.remove();}, "Thread-1");Thread thread2 = new Thread(() -> {for (int i = 0; i < 5; i++) {int value = threadLocal.get();threadLocal.set(value + 2);System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());}threadLocal.remove();}, "Thread-2");thread1.start();thread2.start();}
}

输出结果示例

Thread-1: 1
Thread-2: 2
Thread-1: 2
Thread-2: 4
Thread-1: 3
Thread-2: 6
Thread-1: 4
Thread-2: 8
Thread-1: 5
Thread-2: 10

二、ThreadLocal 核心方法解析

2.1 get()方法

获取当前线程的变量副本。如果是首次调用,会调用initialValue()方法进行初始化。

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();
}

2.2 set(T value)方法

设置当前线程的变量副本值。

public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {map.set(this, value);} else {createMap(t, value);}
}

2.3 remove()方法

移除当前线程的变量副本,防止内存泄漏。

public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null) {m.remove(this);}
}

2.4 initialValue()方法

返回当前线程变量的初始值,默认实现返回null。可以通过匿名内部类或 Lambda 表达式重写该方法。

protected T initialValue() {return null;
}// 使用withInitial()方法简化初始化
private static final ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);

三、ThreadLocal 底层实现原理

3.1 核心数据结构:ThreadLocalMap

ThreadLocal的核心是通过ThreadLocalMap实现的。每个Thread对象都包含一个ThreadLocalMap实例,用于存储该线程的所有局部变量。

public class Thread implements Runnable {// 每个线程都有自己的ThreadLocalMapThreadLocal.ThreadLocalMap threadLocals = null;
}

3.2 ThreadLocalMap 的结构

ThreadLocalMapThreadLocal的静态内部类,它使用弱引用Entry数组来存储键值对:

static class ThreadLocalMap {static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}private Entry[] table;// ...
}

关键点

  • 键(Key):是ThreadLocal对象的弱引用(WeakReference)。
  • 值(Value):是用户存储的变量副本。
  • 弱引用特性:当ThreadLocal对象的强引用被释放后,键(弱引用)会在下一次 GC 时被回收,但值(强引用)不会被回收,可能导致内存泄漏。

3.3 内存结构示意图

Thread -> ThreadLocalMap -> Entry[] -> [WeakReference<ThreadLocal>, value]

3.4 工作流程

  1. 存储流程
    • 调用ThreadLocal.set(value)时,获取当前线程的ThreadLocalMap
    • ThreadLocal对象为键,存储值到ThreadLocalMap中。
  2. 读取流程
    • 调用ThreadLocal.get()时,获取当前线程的ThreadLocalMap
    • ThreadLocal对象为键,从ThreadLocalMap中获取值。
  3. 移除流程
    • 调用ThreadLocal.remove()时,从当前线程的ThreadLocalMap中移除对应的键值对。

四、ThreadLocal 内存泄漏问题

4.1 内存泄漏的原因

ThreadLocalMapEntry中键是ThreadLocal的弱引用,而值是强引用:

  • ThreadLocal对象的外部强引用被释放后,键(弱引用)会被 GC 回收。
  • 但值(强引用)仍然存在于ThreadLocalMap中,直到线程结束或手动调用remove()

如果线程是线程池中的长期存活线程,就会导致值对象永远无法被回收,造成内存泄漏。

4.2 如何避免内存泄漏

  1. 使用完后及时调用remove()方法

    try {// 使用ThreadLocalthreadLocal.set("value");// 业务逻辑
    } finally {// 确保无论是否发生异常,都能移除ThreadLocalthreadLocal.remove();
    }
    
  2. 优先使用static修饰的ThreadLocal

    • 静态变量的生命周期与类的生命周期一致,可以减少弱引用带来的问题。
  3. 避免在线程池中使用ThreadLocal

    • 如果必须使用,确保在线程执行完毕后调用remove()

五、ThreadLocal 典型应用场景

5.1 数据库连接管理

public class ConnectionManager {private static final ThreadLocal<Connection> connectionHolder = ThreadLocal.withInitial(() -> {try {return DriverManager.getConnection("jdbc:mysql://localhost:3306/test");} catch (SQLException e) {throw new RuntimeException(e);}});public static Connection getConnection() {return connectionHolder.get();}public static void closeConnection() {Connection conn = connectionHolder.get();if (conn != null) {try {conn.close();connectionHolder.remove();} catch (SQLException e) {e.printStackTrace();}}}
}

5.2 用户会话管理

public class SessionManager {private static final ThreadLocal<UserSession> sessionHolder = new ThreadLocal<>();public static void setSession(UserSession session) {sessionHolder.set(session);}public static UserSession getSession() {return sessionHolder.get();}public static void clearSession() {sessionHolder.remove();}
}

5.3 日志跟踪 ID

public class TraceIdInterceptor implements HandlerInterceptor {private static final ThreadLocal<String> traceIdHolder = new ThreadLocal<>();@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {String traceId = UUID.randomUUID().toString();traceIdHolder.set(traceId);// 将traceId放入MDC,用于日志记录MDC.put("traceId", traceId);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {// 清理ThreadLocal和MDCtraceIdHolder.remove();MDC.remove("traceId");}
}

六、InheritableThreadLocal:子线程继承父线程的 ThreadLocal 值

InheritableThreadLocalThreadLocal的子类,它允许子线程继承父线程的ThreadLocal值。

public class InheritableThreadLocalExample {private static final InheritableThreadLocal<Integer> inheritableThreadLocal = new InheritableThreadLocal<>();public static void main(String[] args) {inheritableThreadLocal.set(100);Thread childThread = new Thread(() -> {System.out.println("Child Thread: " + inheritableThreadLocal.get());  // 输出100});childThread.start();System.out.println("Main Thread: " + inheritableThreadLocal.get());  // 输出100}
}

注意事项

  • 子线程的值是在创建时从父线程复制的,之后父线程的修改不会影响子线程。
  • 如果需要动态传递值,可以考虑使用TransmittableThreadLocal(阿里开源的解决方案)。

七、总结

ThreadLocal是 Java 多线程编程中的一个重要工具,它通过为每个线程提供独立的变量副本,解决了多线程环境下的变量共享问题,简化了并发编程模型。

本文深入探讨了ThreadLocal的核心概念、方法使用、底层实现原理以及潜在的内存泄漏问题,并通过代码样例展示了其典型应用场景。在使用ThreadLocal时,需要特别注意内存泄漏问题,确保在线程结束前调用remove()方法。

ThreadLocal的设计思想值得借鉴,它通过空间换时间的方式,避免了多线程竞争带来的性能开销。但同时也要谨慎使用,避免在不合适的场景滥用导致系统复杂度增加。

相关文章:

Java 中的 ThreadLocal 详解:从基础到源码

Java 中的 ThreadLocal 详解&#xff1a;从基础到源码 引言 在 Java 多线程编程中&#xff0c;ThreadLocal是一个经常被提及的概念。它提供了一种线程局部变量的机制&#xff0c;使得每个线程都可以独立地存储和访问自己的变量副本&#xff0c;而不会与其他线程产生冲突。本文…...

(二)开启深度学习动手之旅:先筑牢预备知识根基

1 数据操作 数据操作是深度学习的基础&#xff0c;包括数据的创建、索引、切片、运算等操作。这些操作是后续复杂模型构建和训练的前提。 入门 &#xff1a;理解如何使用NumPy创建数组&#xff0c;这是深度学习中数据存储的基本形式。掌握数组的属性&#xff08;如数据类型dt…...

Spring Boot3.4.1 集成redis

Spring Boot3.4.1 集成redis 第一步 引入依赖 <!-- redis 缓存操作 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- pool 对象池 …...

【Prometheus+Grafana实战:搭建监控系统(含告警配置)】

什么是Prometheus和Grafana&#xff1f; Prometheus&#xff1a;一款开源的监控告警工具&#xff0c;擅长时序数据存储和多维度查询&#xff08;通过PromQL&#xff09;&#xff0c;采用Pull模型主动抓取目标指标。Grafana&#xff1a;数据可视化平台&#xff0c;支持多种数据…...

操作系统原理第9章 磁盘存储器管理 重点内容

目录 &#xff08;一&#xff09;外存的组织方式种类 &#xff08;二&#xff09;FAT 系统&#xff08;计算&#xff09; &#xff08;三&#xff09;文件存储空间的管理方式 &#xff08;一&#xff09;外存的组织方式种类 连续组织方式 原理&#xff1a;在磁盘等外存上&…...

一文速通Python并行计算:11 Python多进程编程-进程之间的数据安全传输-基于队列和管道

一文速通 Python 并行计算&#xff1a;11 Python 多进程编程-进程之间的数据安全传输-基于队列和管道 摘要&#xff1a; Python 多进程中&#xff0c;Queue 和 Pipe 提供进程间安全通信。Queue 依赖锁和缓冲区&#xff0c;保障数据原子性和有序性&#xff1b;Pipe 实现点对点单…...

LangChain-Tool和Agent结合智谱AI大模型应用实例2

1.Tool(工具) 定义与功能 单一功能模块:Tool是完成特定任务的独立工具,每个工具专注于一项具体的操作,例如:搜索、计算、API调用等 无决策能力:工具本身不决定何时被调用,仅在被触发时执行预设操作 输入输出明确:每个工具需明确定义输入、输出参数及格式 2.Agent(…...

HTML、XML、JSON 是什么?有什么区别?又是做什么的?

在学习前端开发或者理解互联网工作原理的过程中&#xff0c;我们经常会遇到三个非常重要的概念&#xff1a;HTML、XML 和 JSON。它们看起来有点像&#xff0c;但其实干的事情完全不同。 &#x1f3c1; 一、他们是谁&#xff1f;什么时候诞生的&#xff1f; 名称全称诞生时间谁…...

C++中IO文件输入输出知识详解和注意事项

以下内容将从文件流类体系、打开模式、文本与二进制 I/O、随机访问、错误处理、性能优化等方面&#xff0c;详解 C 中文件输入输出的使用要点&#xff0c;并配以示例。 一、文件流类体系 C 标准库提供三种文件流类型&#xff0c;均定义在 <fstream> 中&#xff1a; std…...

centos7.6阿里云镜像各个版本介绍

&#xff08;水一期&#xff09; Index of /centos-vault/centos/7.6.1810/isos/x86_64/ File NameFile SizeDateParent directory/--0_README.txt2.4 KB2018-12-01 21:21CentOS-7-x86_64-DVD-1810.iso4.3 GB2018-11-26 07:55CentOS-7-x86_64-DVD-1810.torrent86.0 KB2018-12-…...

InnoDB引擎逻辑存储结构及架构

简化理解版 想象 InnoDB 是一个高效运转的仓库&#xff1a; 核心内存区 (大脑 & 高速缓存 - 干活超快的地方) 缓冲池 Buffer Pool (最最核心&#xff01;)&#xff1a; 作用&#xff1a; 相当于仓库的“高频货架”。把最常用的数据&#xff08;表数据、索引&#xff09;从…...

KVM——CPU独占

文章目录 机器现况信息配置CPU独占(pin)启用 CPU 独占&#xff08;隔离&#xff09;验证 机器现况信息 [rootkvm-server ~]# virsh list --allId 名称 状态 --------------------------- CULinux-VM 关闭- ubuntu20.04 关闭- ubuntu24.04 关闭[roo…...

第4讲、Odoo 18 模块系统源码全解与架构深度剖析【modules】

引言 Odoo 是一款强大的开源企业资源规划&#xff08;ERP&#xff09;与客户关系管理&#xff08;CRM&#xff09;系统&#xff0c;其核心竞争力之一在于高度模块化的架构设计。模块系统不仅是 Odoo 框架的基石&#xff0c;更是实现功能灵活扩展与定制的关键。本文将结合 Odoo…...

pytorch简单线性回归模型

模型五步走 1、获取数据 1. 数据预处理 2.归一化 3.转换为张量 2、定义模型 3、定义损失函数和优化器 4、模型训练 5、模型评估和调优 调优方法 6、可视化&#xff08;可选&#xff09; 示例代码 import torch import torch.nn as nn import numpy as np import matplot…...

在 HTML 文件中添加图片的常用方法

本文详解HTML图片插入方法&#xff1a;1&#xff09;通过<img>标签实现&#xff0c;必须含src和alt属性&#xff1b;2&#xff09;路径支持绝对/相对引用&#xff1b;3&#xff09;建议设置width/height保持比例&#xff1b;4&#xff09;响应式方案用srcset适配不同设备…...

四、web安全-行业术语

1. 肉鸡 所谓“肉鸡”是一种很形象的比喻&#xff0c;比喻那些可以随意被我们控制的电脑&#xff0c;对方可以是WINDOWS系统&#xff0c;也可以是UNIX/LINUX系统&#xff0c;可以是普通的个人电脑&#xff0c;也可以是大型的服务器&#xff0c;我们可以象操作自己的电脑那样来…...

Kafka核心技术解析与最佳实践指南

Apache Kafka作为分布式流处理平台的核心组件&#xff0c;以其高吞吐、低延迟和可扩展性成为现代数据架构的基石。本文基于Kafka官方文档&#xff0c;深度解析其核心技术原理&#xff0c;并结合实践经验总结关键技巧与最佳实践。 Kafka的高性能源于其精巧的架构设计&#xff0…...

Unity基础学习(十二)Unity 物理系统之范围检测

目录 一、关于范围检测的主要API&#xff1a; 1. 盒状范围检测 Physics.OverlapBox 2. 球形范围检测 Physics.OverlapSphere 3. 胶囊范围检测 Physics.OverlapCapsule 4. 盒状检测 NonAlloc 版 5. 球形检测 NonAlloc 版 6. 胶囊检测 NonAlloc 版 二、关于API中的两个重…...

JVM 的垃圾回收机制 GC

C/C 这样的编程语言中,申请内存的时候,是需要用完了,进行手动释放的 C 申请内存 1)局部变量(不需要手动释放) 2)全局变量(不需要手动释放) 3)动态申请 malloc(通过 free 进行释放的) C 申请内存 1)局部变量 2)全局变量/静态变量 3)动态申请 new 通过 delete 进行释放 …...

TypeScript 针对 iOS 不支持 JIT 的优化策略总结

# **TypeScript 针对 iOS 不支持 JIT 的优化策略总结** 由于 iOS 的 **JavaScriptCore (JSC)** 引擎 **禁用 JIT&#xff08;Just-In-Time 编译&#xff09;**&#xff0c;JavaScript 在 iOS 上的执行性能较差&#xff0c;尤其是涉及动态代码时。 **TypeScript&#xff08;T…...

00 QEMU源码中文注释与架构讲解

QEMU源码中文注释与架构讲解 先占坑&#xff1a;等后续完善后再更新此文章 注释作者将狼才鲸创建日期2025-05-30更新日期NULL CSDN阅读地址&#xff1a;00 QEMU源码中文注释与架构讲解Gitee源码仓库地址&#xff1a;才鲸嵌入式/qemu 一、前言 参考网址 QEMU 源码目录简介qe…...

ansible template 文件中如果包含{{}} 等非ansible 变量处理

在 Ansible 模板中&#xff0c;如果你的 Python 脚本里有大量 {}、f""、或者其他 Jinja 会误解析的语法&#xff0c;就需要用 {% raw %}…{% endraw %} 把它们包起来&#xff0c;只在需要替换变量的那一行单独“放行”。例如&#xff1a; {% raw %} #!/usr/bin/env …...

Screen 连接远程服务器(Ubuntu)

连接 1. 安装screen 默认预安装&#xff0c;可以通过命令查看&#xff1a; screen --version 若未安装&#xff1a; # Ubuntu/Debian sudo apt-get install screen 2. 本机连接远程服务器 ssh root192.168.x.x 在远程服务器中打开screen&#xff1a; screen -S <nam…...

路由器、网关和光猫三种设备有啥区别?

无论是家中Wi-Fi信号的覆盖&#xff0c;还是企业网络的高效运行&#xff0c;路由器、网关和光猫这些设备都扮演着不可或缺的角色。然而&#xff0c;对于大多数人来说&#xff0c;这三者的功能和区别却像一团迷雾&#xff0c;似懂非懂。你是否曾疑惑&#xff0c;为什么家里需要光…...

vscode实时预览编辑markdown

vscode实时预览编辑markdown 点击vsode界面&#xff0c;实现快捷键如下&#xff1a; 按下快捷键 CtrlShiftV&#xff08;Windows/Linux&#xff09;或 CommandShiftV&#xff08;Mac&#xff09;即可在侧边栏打开 Markdown 预览。 效果如下&#xff1a;...

2505软考高项第一、二批真题终极汇总

第一批2025.05综合题&#xff08;75道选择题&#xff09; 1、2025 年中央一号文件对进一步深化农村改革的各项任务作出全面部署。“推进农业科技力量协同攻关”的相关措施不包括()。 A.强化农业科研资源力量统筹&#xff0c;培育农业科技领军企业 B.发挥农业科研平台作用&…...

云原生安全基础:Linux 文件权限管理详解

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 在云原生环境中&#xff0c;Linux 文件权限管理是保障系统安全的核心技能之一。无论是容器化应用、微服务架构还是基础设施即代码&#xff08;IaC&#xf…...

A类地址中最小网络号(0.x.x.x) 默认路由 / 无效/未指定地址

A类地址中最小网络号&#xff08;0.x.x.x&#xff09;为何不指派&#xff1f; 在IPv4的A类地址中&#xff0c;网络号范围为 0.0.0.0 ~ 127.0.0.0&#xff0c;但网络号0&#xff08;即0.x.x.x&#xff09; 通常不被指派给任何网络&#xff0c;原因如下&#xff1a; 1. 0.x.x.x …...

[嵌入式实验]实验二:LED控制

一、实验目的 1.熟悉开发环境 2.控制LED灯 二、实验环境 硬件&#xff1a;STM32开发板、CMSIS-DAP调试工具 软件&#xff1a;ARM的IDE&#xff1a;Keil C51 三、实验内容 1.实验原理 &#xff08;1&#xff09;LED灯原理与点亮 LED即发光二极管&#xff0c;有电流通过…...

6.4.2_3最短路径问题_Floyd算法

Floyd弗洛伊德 膜拜大佬&#xff0c;给大佬鞠躬鞠躬鞠躬。。。。。。。。。 Floyd算法 ----解决顶点间的最短路径&#xff1a; 过程&#xff1a; 如下&#xff1a; 初始化(没有中转点)&#xff1a;2个邻接矩阵A和path&#xff0c;第一个是没有中转点的2个顶点之间的最短路径…...