线程池(六):ThreadLocal相关知识详解
线程池(六):ThreadLocal相关知识详解
- 线程池(六):ThreadLocal相关知识详解
- 一、概述
- 定义与作用
- 应用场景
- 二、ThreadLocal基本使用
- 创建ThreadLocal对象
- 设置和获取值
- 初始化值
- 完整示例
- 三、ThreadLocal的实现原理&源码解析
- 内部结构
- set方法源码解析
- get方法源码解析
- remove方法源码解析
- 弱引用的作用
- 四、ThreadLocal内存泄露问题
- 内存泄露产生原因
- 如何避免内存泄露
线程池(六):ThreadLocal相关知识详解
一、概述
定义与作用
ThreadLocal是Java中的一个类,它提供了线程本地变量的功能。简单来说,每个使用ThreadLocal的线程都拥有自己独立的变量副本,各个线程之间的变量副本相互隔离,互不干扰。这就解决了多线程环境下变量共享的冲突问题。
在多线程编程中,当多个线程同时访问一个共享变量时,可能会出现数据不一致等并发问题。而ThreadLocal可以让每个线程都有自己专属的变量,就好像每个线程都“私藏”了一份变量,各自使用各自的,从根本上避免了线程间对共享变量的竞争。
应用场景
- 数据库连接:在一个Web应用中,每个请求通常由一个线程来处理。为了避免多个线程之间数据库连接的混乱,我们可以使用ThreadLocal来为每个线程保存一个独立的数据库连接。这样每个线程在处理请求过程中,使用的都是自己的数据库连接,不会相互干扰。
- 用户会话信息:在Web开发中,需要记录当前用户的会话信息,比如用户的登录状态、用户ID等。使用ThreadLocal可以方便地在一个线程处理的整个流程中随时获取和设置这些会话信息,并且不同用户的请求线程之间的会话信息不会混淆。
- 事务管理:在涉及事务的操作中,确保一个事务内的一系列操作都在同一个数据库连接上进行是很重要的。通过ThreadLocal可以将事务相关的数据库连接绑定到当前线程,在事务的各个操作环节中,都能使用到同一个连接,保证事务的一致性。
二、ThreadLocal基本使用
创建ThreadLocal对象
创建ThreadLocal对象非常简单,只需要使用new
关键字即可。示例代码如下:
ThreadLocal<String> threadLocal = new ThreadLocal<>();
这里创建了一个ThreadLocal
对象,它存储的是String
类型的变量。
设置和获取值
- 设置值:通过
set
方法可以为当前线程设置ThreadLocal
变量的值。示例如下:
threadLocal.set("Hello, ThreadLocal!");
这行代码会将字符串"Hello, ThreadLocal!"
设置为当前线程对应的ThreadLocal
变量的值。
- 获取值:使用
get
方法可以获取当前线程中ThreadLocal
变量的值。示例如下:
String value = threadLocal.get();
System.out.println(value);
如果当前线程还没有设置过值,get
方法会返回null
。
初始化值
有时候我们希望在ThreadLocal
被创建时就有一个初始值,而不是等到第一次set
操作。可以通过继承ThreadLocal
并重写initialValue
方法来实现。示例代码如下:
ThreadLocal<String> initializedThreadLocal = new ThreadLocal<>() {@Overrideprotected String initialValue() {return "Initial value";}
};
String initialValue = initializedThreadLocal.get();
System.out.println(initialValue); // 输出:Initial value
或者使用Java 8引入的withInitial
方法:
ThreadLocal<String> anotherThreadLocal = ThreadLocal.withInitial(() -> "Another initial value");
String anotherInitValue = anotherThreadLocal.get();
System.out.println(anotherInitValue); // 输出:Another initial value
完整示例
下面是一个完整的示例,展示了多个线程使用ThreadLocal
的情况:
public class ThreadLocalExample {private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();public static void main(String[] args) {Thread thread1 = new Thread(() -> {threadLocal.set(10);System.out.println("Thread1: " + threadLocal.get());});Thread thread2 = new Thread(() -> {threadLocal.set(20);System.out.println("Thread2: " + threadLocal.get());});thread1.start();thread2.start();}
}
在这个示例中,thread1
和thread2
两个线程分别设置并获取自己线程内ThreadLocal
变量的值,它们之间互不影响。
三、ThreadLocal的实现原理&源码解析
内部结构
- Thread类中的ThreadLocalMap:在
Thread
类中,有一个名为threadLocals
的成员变量,它的类型是ThreadLocal.ThreadLocalMap
。这是一个专门为ThreadLocal
设计的定制化的哈希映射表。每个线程都有自己独立的ThreadLocalMap
,用于存储该线程中所有ThreadLocal
变量及其对应的值。 - ThreadLocalMap的Entry:
ThreadLocalMap
内部使用Entry
数组来存储数据。Entry
是ThreadLocalMap
的静态内部类,它继承自WeakReference<ThreadLocal<?>>
。每个Entry
对象包含一个对ThreadLocal
对象的弱引用(作为键)和对应的值。
set方法源码解析
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}
- 首先获取当前线程
Thread t = Thread.currentThread();
,这是因为ThreadLocal
变量是与线程绑定的,每个线程都有自己的副本。 - 然后通过
getMap(t)
方法获取当前线程的ThreadLocalMap
。getMap
方法的实现如下:
ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}
它直接返回当前线程的threadLocals
成员变量,即该线程的ThreadLocalMap
。
3. 如果ThreadLocalMap
不为null
,则调用map.set(this, value)
将当前ThreadLocal
对象(this
)作为键,传入的值value
作为值,存入ThreadLocalMap
中。map.set
方法的实现较为复杂,主要是处理哈希冲突等情况,大致思路是通过计算ThreadLocal
对象的哈希值,找到对应的数组索引位置,如果该位置已经有元素(发生哈希冲突),则通过线性探测等方式寻找下一个可用的位置来存储。
4. 如果当前线程的ThreadLocalMap
为null
,则调用createMap(t, value)
方法创建一个新的ThreadLocalMap
,并将当前ThreadLocal
对象和值存入其中。createMap
方法的实现如下:
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}
它创建了一个新的ThreadLocalMap
对象,并将其赋值给当前线程的threadLocals
成员变量。
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();
}
- 同样先获取当前线程
Thread t = Thread.currentThread();
,再获取当前线程的ThreadLocalMap
。 - 如果
ThreadLocalMap
不为null
,则通过map.getEntry(this)
方法获取与当前ThreadLocal
对象对应的Entry
。getEntry
方法主要是根据ThreadLocal
对象的哈希值在Entry
数组中查找对应的元素。 - 如果找到了对应的
Entry
,则将其中存储的值转换为相应类型并返回。 - 如果没有找到对应的
Entry
(可能是因为还没有设置过值等原因),则调用setInitialValue
方法。setInitialValue
方法的实现如下:
private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;
}
它先调用initialValue
方法获取初始值(如果之前重写过initialValue
方法),然后将这个初始值存入当前线程的ThreadLocalMap
中,并返回该初始值。
remove方法源码解析
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);
}
- 先获取当前线程的
ThreadLocalMap
。 - 如果
ThreadLocalMap
不为null
,则调用m.remove(this)
方法从ThreadLocalMap
中移除与当前ThreadLocal
对象对应的键值对。remove
方法会根据ThreadLocal
对象的哈希值找到对应的Entry
,并将其从数组中移除,同时还会处理一些相关的清理和调整工作,以保证ThreadLocalMap
的正常结构和性能。
弱引用的作用
ThreadLocalMap
中的Entry
继承自WeakReference<ThreadLocal<?>>
,这意味着ThreadLocal
对象作为键是被弱引用的。当没有其他强引用指向ThreadLocal
对象时,在下次垃圾回收时,这个ThreadLocal
对象就会被回收。这样设计的好处是可以避免内存泄漏。例如,当一个ThreadLocal
对象不再被使用(没有强引用指向它),如果不是弱引用,那么即使线程结束了,ThreadLocalMap
中仍然会持有这个ThreadLocal
对象的引用,导致该对象无法被回收,从而造成内存泄漏。而使用弱引用,在合适的时候可以让ThreadLocal
对象被垃圾回收器回收,减少内存占用。
四、ThreadLocal内存泄露问题
内存泄露产生原因
虽然ThreadLocalMap
中对ThreadLocal
对象采用了弱引用,在一定程度上可以避免内存泄漏,但如果使用不当,仍然可能会出现内存泄漏问题。主要原因在于Entry
中的值(value
)是强引用。当ThreadLocal
对象被垃圾回收后,Entry
中对应的键(ThreadLocal
对象的弱引用)变为null
,但值(value
)仍然存在于ThreadLocalMap
中,如果后续没有对这些无效的Entry
进行清理,那么这些值就会一直占用内存,导致内存泄漏。
例如,在线程池场景中,线程是复用的。如果一个线程使用了ThreadLocal
,并设置了值,当这个线程执行完任务被放回线程池等待下一次任务时,即使ThreadLocal
对象本身已经没有强引用(可以被垃圾回收),但线程的ThreadLocalMap
中仍然保留着之前设置的值的强引用。如果不进行清理,随着线程池不断复用线程,这些无效的值就会不断累积,占用越来越多的内存。
如何避免内存泄露
- 手动调用remove方法:在使用完
ThreadLocal
变量后,及时调用remove
方法。例如在Web应用中,在一个请求处理完成后,在相关的拦截器或者业务代码中调用ThreadLocal
的remove
方法,将当前线程中ThreadLocal
变量对应的值从ThreadLocalMap
中移除。示例代码如下:
public class MemoryLeakAvoidanceExample {private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();public static void main(String[] args) {Thread thread = new Thread(() -> {try {threadLocal.set(10);// 模拟业务逻辑处理// ...} finally {threadLocal.remove();}});thread.start();}
}
在这个示例中,使用finally
块确保无论业务逻辑是否正常执行完毕,都会调用remove
方法,及时清理ThreadLocal
变量对应的值。
2. 使用try - finally代码块:在使用ThreadLocal
的代码块中,尽量使用try - finally
结构。在try
块中进行正常的业务操作,包括设置和获取ThreadLocal
变量的值,在finally
块中调用remove
方法。这样可以保证在任何情况下,即使出现异常,也能正确地清理ThreadLocal
变量,避免内存泄漏。
3. 在线程池场景中的特殊处理:对于线程池场景,由于线程会被复用,更需要注意ThreadLocal
的清理。可以自定义线程池,在任务执行前设置ThreadLocal
变量,在任务执行完成后,通过线程池的钩子方法(如afterExecute
方法)来调用ThreadLocal
的remove
方法,确保每次任务执行完毕后都能清理相关的ThreadLocal
变量。以下是一个简单的自定义线程池示例:
import java.util.concurrent.*;public class CustomThreadPoolExecutor extends ThreadPoolExecutor {public CustomThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);}@Overrideprotected void afterExecute(Runnable r, Throwable t) {// 假设这里有一个全局的ThreadLocal变量需要清理ThreadLocal<Integer> threadLocal = new ThreadLocal<>();threadLocal.remove();super.afterExecute(r, t);}
}
通过这种方式,可以在多线程复用的场景下有效地避免ThreadLocal
导致的内存泄漏问题。
综上所述,ThreadLocal在多线程编程中是一个非常有用的工具,通过合理使用它可以方便地实现线程本地变量的管理,但同时也需要注意其实现原理和可能出现的内存泄漏问题,通过正确的使用方式来确保程序的性能和稳定性。
相关文章:
线程池(六):ThreadLocal相关知识详解
线程池(六):ThreadLocal相关知识详解 线程池(六):ThreadLocal相关知识详解一、概述定义与作用应用场景 二、ThreadLocal基本使用创建ThreadLocal对象设置和获取值初始化值完整示例 三、ThreadLocal的实现原…...

WSL 中 nvidia-smi: command not found的解决办法
前言 在使用基于 Linux 的 Windows 子系统(WSL)时,当我们执行某些操作后,可能会遇到输入 nvidia-smi 命令却无法被系统识别的情况。 例如,在终端中输入nvidia-smi 后,系统返回提示 -bash: nvidia-smi: co…...

FPGA前瞻篇-组合逻辑电路设计-多路复用器
多路选择器(MUX)简介 基本概念 多路选择器(MUX,Multiplexer)是一种多输入、单输出的组合逻辑电路。 它通过选择控制信号,在多个输入信号中选择一个连接到输出端。 可以理解为一个多路数字开关。 &…...
作为高速通道光纤传输模式怎么理解以及到底有哪些?
光纤的传输模式主要取决于光纤的结构(如纤芯直径和折射率分布),不同模式对应光波在光纤中传播的不同路径和电磁场分布。以下是光纤传输模式的主要分类及特点: 1. 单模光纤(Single-Mode Fiber, SMF) 核心特点: 纤芯直径极小(通常为 8-10微米),仅允许光以单一模式(…...

【Castle-X机器人】五、物联网模块配置与调试
持续更新。。。。。。。。。。。。。。。 【Castle-X机器人】五、物联网模块配置与调试 五、物联网模块配置与调试5.1 物联网模块调试物联网模块测试:控制物联网模块:物联网模块话题五、物联网模块配置与调试 5.1 物联网模块调试 调试前需确保Castle-x与mqtt主机服务器处于同…...
马架构的Netty、MQTT、CoAP面试之旅
标题:马架构的Netty、MQTT、CoAP面试之旅 在互联网大厂的Java求职者面试中,一位名叫马架构的资深Java架构师正接受着严格的考验。他拥有十年的Java研发经验和架构设计经验,尤其对疑难问题和线索问题等有着丰富的经历。 第一轮提问ÿ…...

20250426在ubuntu20.04.2系统上打包NanoPi NEO开发板的FriendlyCore系统刷机eMMC的固件
20250426在ubuntu20.04.2系统上打包NanoPi NEO开发板的FriendlyCore系统刷机eMMC的固件 2025/4/26 21:30 缘起:使用NanoPi NEO开发板,编译FriendlyCore系统,打包eMMC固件的时候报错。 1、在ubuntu14.04下git clone异常该如何处理呢ÿ…...

JAVA---字符串
ctrlN 搜索界面(idea) API和API帮助文档 API : 应用程序编程接口(换句话说,就是别人已经写好了,我们不需要再编写,直接使用即可) Java API :就是JDK中提供的各种功能…...

MacOS 10.15上能跑大语言模型吗?
MacOS 10.15上能跑大语言模型吗? 下载安装Ollama运行大语言模型引申出的问题 MacOS 10.15.7(发布于2020年9月)作为已经发布了将近5年的系统版本能够运行当今流行的大语言模型吗?这篇文章简要介绍了在MacOS 10.15上通过Ollama运行d…...

AI Agent开发第37课-DeepSeek的多模态版JanusPro-7B本地安装
开篇 搜遍Janus Pro git issues、谷哥、国内网络,教程全都是错的。因此还是决定写一本全网唯一正确的教程。 目前网上的教程包括外网的教程都是“缺斤少量”,按照那些教程操作下来不是装不起来,就是装起来只能CPU运行,或者运行起来了Janus的Web前端老是转啊转不出内容。 …...

神经网络笔记 - 感知机
一 感知机是什么 感知机(Perceptron)是一种接收输入信号并输出结果的算法。 它根据输入与权重的加权和是否超过某个阈值(threshold),来判断输出0还是1。 二.计算方式 感知机的基本公式如下: X1, X2 : …...

阿里云基于本地知识库构建RAG应用 | 架构与场景
RAG(检索增强生成,Retrieval-Augmented Generation)是一种结合了检索和生成技术的框架,旨在通过外部知识库的检索来增强大语言模型(LLM)的生成能力。 其核心架构包括两个主要部分: 检索模块&a…...

CSS简单实用的加载动画、骨架屏有效果图
效果图 .wxml <!-- 骨架屏 --> <view wx:for"{{skeleton}}" wx:key"index" class"container center" style"--w:{{item.w}}rpx;--h:{{item.h}}rpx" /> <!-- 加载 --> <view class"arco-loading center&quo…...

3:QT联合HALCON编程—海康相机SDK二次程序开发
思路: 1.定义带UI界面的主函数类 1.1在主函数中包含其它所有类头文件,进行声明和实例化;使用相机时,是用公共相机的接口在某一个具体函数中去实例化具体的海康相机对象。 1.2设计界面:连接相机,单次采集&a…...

【前后端分离项目】Vue+Springboot+MySQL
文章目录 1.安装 Node.js2.配置 Node.js 环境3.安装 Node.js 国内镜像4.创建 Vue 项目5.运行 Vue 项目6.访问 Vue 项目7.创建 Spring Boot 项目8.运行 Spring Boot 项目9.访问 Spring Boot 项目10.实现 Vue 与 Spring Boot 联动11.安装 axios12.编写请求13.调用函数请求接口14.…...

数据结构和算法(八)--2-3查找树
目录 一、平衡树 1、2-3查找树 1.1、定义 1.2、查找 1.3、插入 1.3.1、向2-结点中插入新键 1.3.2、向一棵只含有一个3-结点的树中插入新键 1.3.3、向一个父结点为2-结点的3-结点中插入新键 1.3.4、向一个父结点为3-结点的3-结点中插入新键 1.3.5、分解根结点 1.4、2…...
Java爬虫入门:从网页抓取到数据提取(正则表达式篇)
在当今信息爆炸的时代,如何从浩瀚的互联网中快速、准确地获取所需数据成为了一个重要的技能。网络爬虫技术应运而生,它允许我们自动化地访问网页并提取其中的信息。Java作为一门功能强大且拥有丰富生态的编程语言,在构建网络爬虫方面也表现出…...

Unity-Shader详解-其二
前向渲染和延迟渲染 前向渲染和延迟渲染总的来说是我们的两种主要的渲染方式。 我们在Unity的Project Settings中的Graphic界面能够找到渲染队列的设定: 我们也可以在Main Camera这里进行设置: 那这里我们首先介绍一下两种渲染(Forward R…...

深入浅出理解并应用自然语言处理(NLP)中的 Transformer 模型
1 引言 随着信息技术的飞速发展,自然语言处理(Natural Language Processing, NLP)作为人工智能领域的一个重要分支,已经取得了长足的进步。从早期基于规则的方法到如今的深度学习技术,NLP 正在以前所未有的速度改变着我…...

当自动驾驶遇上“安全驾校”:NVIDIA如何用技术给无人驾驶赋能?
自动驾驶技术的商业化落地,核心在于能否通过严苛的安全验证。国内的汽车企业其实也在做自动驾驶,但是吧,基本都在L2级别。换句话说就是在应急时刻内,还是需要人来辅助驾驶,AI驾驶只是决策层,并不能完全掌握…...
WIN11安装Ubuntu22.04双系统,驱动cuda,配置3D GS
前言:看了很多基于3D GS开发的SLAM系统都默认在Ubuntu20.04-22.04中运行,并且WIN11不太方便安装cmake等基础编译库。所以还是在Ubuntu系统中进行咱的研究吧。 step 1. WIN11安装Ubuntu双系统 首先确认系统是否为UEFI模式。 winX进行磁盘管理ÿ…...

【OSG学习笔记】Day 9: 状态集(StateSet)与渲染优化 —— 管理混合、深度测试、雾效等渲染状态
干货开始。_ 一、StateSet核心概念与作用 StateSet 是OSG(OpenSceneGraph)中管理渲染状态的核心类,用于封装 OpenGL 渲染状态(如混合、深度测试、雾效、材质、纹理、着色器等),并将这些状态应用于节点或几何体。 通过合理组织 StateSet,可实现: 渲染状态的高效复用:…...
如何解析商品详情页面
解析商品详情页面是爬虫开发中的一个重要环节。由于商品详情页面通常包含丰富的信息,如商品名称、价格、描述、图片等,因此需要仔细分析页面结构并提取所需数据。以下是一个详细的步骤指南,展示如何使用 Java 和 Jsoup 解析商品详情页面。 一…...

Operating System 实验七 Linux文件系统实验
实验目标: 使用dd命令创建磁盘镜像文件ext2.img并格式化为ext2文件系统,然后通过mount命令挂载到Linux主机文件系统。查看ext2文件系统的超级块的信息,以及数据块的数量、数据块的大小、inode个数、空闲数据块的数量等信息 在文件系统中创建文件xxxxx.txt(其中xxxxx为你的学…...
使用 IntersectionObserver 实现懒加载提升网页性能的高效方案
在当今快节奏的网络环境中,用户对于网页加载速度的要求越来越高。对于前端开发者而言,优化网页性能、减少初始加载时间成为了一项至关重要的任务。懒加载(Lazy Loading)作为一种有效的性能优化策略,能够延迟非关键资源…...
Python-Django系列—部件
部件是 Django 对 HTML 输入元素的表示。部件处理 HTML 的渲染,以及从对应于部件的 GET/POST 字典中提取数据。 内置部件生成的 HTML 使用 HTML5 语法,目标是 <!DOCTYPE html>。例如,它使用布尔属性,如 checked…...

linux中shell脚本的编程使用
linux中shell脚本的编程使用 1.shell的初步理解1.1 怎么理解shell1.2 shell命令 2.shell编程2.1 什么是shell编程2.2 C语言编程 和 shell编程的区别 3.编写和运行第一个shell脚本程序3.1 编写时需要注意以下几点:3.1.1 shell脚本没有main函数,没有头文件…...

图像畸变-径向切向畸变实时图像RTSP推流
实验环境 注意:ffmpeg进程stdin写入两张图片的时间间隔不能太长,否则mediamtx会出现对应的推流session超时退出。 实验效果 全部代码 my_util.py #进度条 import os import sys import time import shutil import logging import time from datetime i…...

手搓雷达图(MATLAB)
看下别人做出来什么效果 话不多说,咱们直接开始 %% 可修改 labels {用户等级, 发帖数, 发帖频率, 点度中心度, 中介中心度, 帖子类型计分, 被列为提案数}; cluster_centers [0.8, 4.5, 3.2, 4.0, 3.8, 4.5, 4.2; % 核心用户0.2, 0.5, 0.3, 0.2, 0.1, 0.0, 0.0;…...

汽车零配件供应商如何通过EDI与主机厂生产采购流程结合
当前,全球汽车产业正经历深刻的数字化转型,供应链协同模式迎来全新变革。作为产业链核心环节,汽车零部件供应商与主机厂的高效对接已成为企业发展的战略要务。然而,面对主机厂日益严格的数字化采购要求,许多供应商在ED…...