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

ThreadLocal原理

关键点总结:

  • ThreadLocal更像是对其他类型变量的一层包装,通过ThreadLocal的包装使得该变量可以在线程之间隔离当前线程全局共享
  • Thread中有一个threadLocals变量,类型为ThreadLocal.ThreadLocalMapThreadLocalMapkeyThreadLocal,value是存入的变量值。
  • ThreadthreadLocalsThreadLocal维护。
  • 每个线程的本地变量不是存储在ThreadLocal示例里边的,而是存放在调用线程的threadLocals变量里边。
  • LoreadLocal类型的本地变量存放在具体的线程内存空间中。ThreadLocal就是一个工具,通过set方法把value值放入调用线程的threadLocals变量。调用**get**方法再从当前线程的threadLocals中拿出数据。
  • 如果调用线程一直不终止,那么这个本地变量会一直存放在调用线程的thradLocals变量里边,所以当不需要使用本地变量的时候可以通过调用ThreadLocalremove方法删除本地变量
  • Java中的内存泄露的情况:长生命周期的对象持有短生命周期的对象的引用就很有可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是Java中内存泄露发生的场景。

📚概述

ThreadLocal:线程本地变量,用于解决多线程并发访问时共享变量的问题。**

📗ThreadLocal实现原理

📐存储原理

  • Thread中有一个threadLocals变量,类型为ThreadLocal.ThreadLocalMapThreadLocalMapkeyThreadLocal,value是存入的变量值。
  • ThreadthreadLocalsThreadLocal维护。
  • 每个线程的本地变量不是存储在ThreadLocal示例里边的,而是存放在调用线程的threadLocals变量里边。
  • LoreadLocal类型的本地变量存放在具体的线程内存空间中。ThreadLocal就是一个工具,通过set方法把value值放入调用线程的threadLocals变量。调用**get**方法再从当前线程的threadLocals中拿出数据。
  • 如果调用线程一直不终止,那么这个本地变量会一直存放在调用线程的thradLocals变量里边,所以当不需要使用本地变量的时候可以通过调用ThreadLocalremove方法删除本地变量

💡ThreadLocal常用方法

  • set(T value):设置线程本地变量的内容。
  • get():获取线程本地变量的内容。
  • remove():移除线程本地变量。注意在线程池的线程复用场景中在线程执行完毕时一定要调用remove,避免在线程被重新放入线程池中时被本地变量的旧状态仍然被保存。

Question1:为什么get()remove()方法没有入参?

Question2ThreadLocal对象作为key存在Thread中,实际中一个线程可能会经过很多方法,如何从Thread中获取到ThreadLocal的数据呢?threadlocal如何使用(没有ThreadLocal时,Thread能获取到存储的数据吗)?

ThreadLocal#set(T value)方法:

public void set(T value) {// 获取当前线程Thread t = Thread.currentThread();// 获取从当前线程中获取ThreadlocalMap,如果不为空存入值(key为当前threadlocal对象实例,value为值),为空则创建一个ThreadLocaMap,key是当前的ThreadLocal ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}// 从Thread中获取ThreadLocalMap
ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}
// 创建ThreadLocalMap 并赋值给Thread的threadLocals属性
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocal#get()方法:

// 以当前ThreadLocal对象为key从Map中获取数据,数据不为空直接返回数据,数据为空,则创建一个key是当前ThreadLocal、value为null的数据存入Map中,并返回null
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();
}
// 创建一个可以是当前ThreadLocal、value为null的数据存入Map中
private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();// 获取线程中的threadLocals变量ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;
}protected T initialValue() {return null;
}

ThreadLocal#remove()方法:

// 从map中移除当前ThreadLocal为key的数据
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);}

总结:
如下图所示,在每个线程内部都有一个名为 threadLocals 的成员变量,该变量的类型为 ThreadLocalMap,其中 key 为我们定义的 ThreadLocal变量的 this 引用,value 则为我门使用 set方法设置的值。每个线程的本地变量存放在线程自己的内存变量threadLocals 中,如果当前线程一直不消亡,那么这些本地变量会一直存在,所以可能会造成内存溢出,因此使用完毕后要记得调用 ThreadLocalremove 方法删除对应线程的 threadLocals 中的本地变量。

📐使用说明

多个Thread与一个ThreadLocal

代码示例:

ThreadLocal<String> content = new ThreadLocal<>();// 线程1
Thread thread = new Thread(() -> {content.set("数据A");System.out.println(Thread.currentThread().getName() +":"+ content.get());
}, "线程1");
thread.start();// 线程2
Thread thread2 = new Thread(() -> {content.set("数据B");System.out.println(Thread.currentThread().getName() +":"+ content.get());}, "线程2");
thread2.start();

输出结果:

线程1:数据A
线程2:数据B

为什么多个线程访问同一个ThreadLocal时,数据不会乱呢,也就是Thread2中不会获取到Thread1中存储的数据?
本质还是因为跟前边给出的存储原理图有关,同一个key,数据存储在不同的Thread中,在不同的Thread中访问,肯定不会访问到其他Thread的数据

同一个Thread与多个ThreadLocal

本质上是一致的,我们只需要记住,真实数据是存放在**Thread****threadLocals**变量中的即可,各个**Thread**中数据互不干扰
多个ThreadLocal时,仅仅是增加了Thread**threadLocals**键值对的数量。

ThreadLocal local1 = new ThreadLocal();
threadLocal.set("数据1");ThreadLocal local2 = new ThreadLocal();
threadLocal2.set("数据2");

💡ThreadLocal使用不当导致内存泄露

内存泄露问题:指程序中动态分配的堆内存由于某种原因没有被释放或者无法释放,造成系统内存的浪费,导致程序运行速度减慢或者系统奔溃等严重后果。内存泄露堆积将会导致内存溢出。

ThreadLocalTreadLocalMapEntry继承了WeakReference
当一个对象被WeakReference包装后,它就产生了一个弱引用指向它。此时即使把强引用切断,仍然有弱引用连接着。但是由于弱引用的特性,这个对象会在下次被GC线程被直接回收。
本质不是**Entry**是弱应用而是**Entry****key**为弱引用。
image.png

使用弱引用的原因

强引用情况:
theadlocalnull被回收时,Thread还存在的情况下,依然存在到达ThreadLocal对象的引用链(ThreadLocalMap中的key),无法清除ThreadLocal的内容,同时ThreadLocalMap中的Value也会保留。在使用线程池的情况下(Thread可能一直存在)可能会出现内存泄露。

弱应用情况:

theadlocalnull被回收时,Thread还存在的情况下,在下次GC时就会将ThreadLocal对象清除,但是ThreadLocalMap中的Value还会保留,导致无法删除。在使用线程池的情况下(Thread可能一直存在)可能会出现内存泄露。

Java中的内存泄露的情况:长生命周期的对象持有短生命周期的对象的引用就很有可能发生内存泄露,尽管短生命周期对象已经不再需要,但是因为长生命周期对象持有它的引用而导致不能被回收,这就是Java中内存泄露发生的场景。

避免内存泄露的方案:

  1. 使用完ThreadLocal,调用其remove方法删除对应的 Entry数据
  2. 使用完ThreadLocal,当前Thread也结束。【注意:该方案在线程池中不行。】

阿里巴巴开发手册中强制规定:
image.png

🎞强引用与弱引用

Java中有4种引用类型:强、软、弱、虚。

  • 强引用不受GC影响,除非引用全部切断。比如 Student s = new Student(),假设当前只有s指向Student对象,那么当s=null时,Student对象会在下次GC时被回收
  • 软引用对象会在内存不足触发GC时被回收(适用于高速缓存)
  • 弱引用是每次GC时都回收,不论内存是否不足
  • 虚引用(堆外内存,比如zerocopy

📗ThreadLocal不支持继承

🗒️演示说明

public class TestThreadLocal {// 创建线程变量public static ThreadLocal<String> threadLocal = new ThreadLocal<>();public static void main(String[] args) {// 设置线程变量threadLocal.set("hello");// 启动子线程new Thread(() -> {System.out.println("child thread:" + threadLocal.get());}, "child thread").start();// 主线程输出线程变量值System.out.println("main:" + threadLocal.get());}}

image.png
同一个 ThreadLocal 变量在父线程中被设置值后,在子线程中是获取不到的,因为在子线程 thread 里面调用 get 方法时当前线程为 thread 线程,而这里调用 set 方法设置线程变量的是 main 线程,两者是不同的线程,自然子线程访问时返回 null。那么有没有办法让子线程能访问到父线程中的值?答案是有,可以通过InheritableThreadLocal实现

🔖解决方案——InheritableThreadLocal类

image.png
InheritableThreadLocal可以解决父子线程中的变量共享。在最开始的类图中,可以看到Thread类中除了有threadLoals变量还有inheritableThreadLocals变量。InheritableThreadLocal继承ThreadLocal,重写了三个方法。操作Thread中的变量由threadLoals变为inheritableThreadLocals
那么具体是如何实现的呢?
image.png

📖参考资料

  1. ThreadLocal原理及使用场景_小机double的博客-CSDN博客_threadlocal使用场景和原理
  2. ThreadLocal的内存泄露?什么原因?如何避免?_BigHong123的博客-CSDN博客_threadlocal会导致内存泄露

相关文章:

ThreadLocal原理

关键点总结&#xff1a; ThreadLocal更像是对其他类型变量的一层包装&#xff0c;通过ThreadLocal的包装使得该变量可以在线程之间隔离和当前线程全局共享。在Thread中有一个threadLocals变量&#xff0c;类型为ThreadLocal.ThreadLocalMap&#xff0c;ThreadLocalMap中key是Th…...

串操作指令详解 MOVS,LODS,STOS,CMPS,SCAS,REP

指令包括&#xff1a;MOVS&#xff0c;LODS&#xff0c;STOS&#xff0c;CMPS&#xff0c;SCAS&#xff0c;REP 串的概念&#xff1a;串是连续存放再内存中的字节块或字块。每个串有一个起始地址和长度&#xff0c; 待操作的数据串称为源串&#xff0c;目的地址称为目标串 目录…...

Java实现判断素数

1 问题 判断101-200之间有多少个素数&#xff0c;并输出所有素数。 2 方法 package homework04; public class Test05 { public static void main(String[] args) { for (int i 101; i < 201; i) { boolean flag true; for (int j 2; j…...

PHP初级教程------------------(2)

目录 运算符 赋值运算符 算术运算符 比较运算符 逻辑运算符 连接运算符 错误抑制符 三目运算符 自操作运算符 ​编辑 计算机码 位运算符 运算符优先级 流程控制 控制分类 顺序结构 分支结构 If分支 ​ Switch分支 循环结构 For循环 while循环 do-while循环 循环控制 ​ …...

【SQL开发实战技巧】系列(三十五):数仓报表场景☞根据条件返回不同列的数据以及Left /Full Join注意事项

系列文章目录 【SQL开发实战技巧】系列&#xff08;一&#xff09;:关于SQL不得不说的那些事 【SQL开发实战技巧】系列&#xff08;二&#xff09;&#xff1a;简单单表查询 【SQL开发实战技巧】系列&#xff08;三&#xff09;&#xff1a;SQL排序的那些事 【SQL开发实战技巧…...

springBoot自动配置过程介绍

什么是自动配置 以前整合spring mybatis框架时候&#xff0c;需要加很多的bean, 比如说sqlSessionFactory等等 现在springboot帮我们干了&#xff0c;我们只需要引入对应的starter就可以了。 springBoot可以帮我们配置好了一些bean. 如mysql, mogondb相关操作等等&#xff…...

PostgreSQL最后的救命稻草 — pg_resetwal

pg_resetwal— 重置 PostgreSQL 数据库集群的预写日志和其他控制信息 适用版本&#xff1a;PostgreSQL 12/13/14/15语法 pg_resetwal [ -f | --force ] [ -n | --dry-run ] [option...] [ -D | --pgdata ]datadir描述pg_resetwal清除预写日志 WAL&#xff0c;并可选地重置pg_c…...

彻底关闭Windows更新

一、关闭Windows Update服务 1、按“Windows R”键&#xff0c;打开运行对话框&#xff0c;并输入“services.msc”&#xff0c;然后再单击“确定”。 2、在弹出的服务窗口中&#xff0c;找到“Windows Update”选项并双击打开它。 3、在弹出的“Windows Update的属性”对话框…...

Java正则表达式语法

Java正则表达式的语法与示例 | |目录 1匹配验证-验证Email是否正确 2在字符串中查询字符或者字符串 3常用正则表达式 4正则表达式语法 1匹配验证-验证Email是否正确 public static void main(String[] args) { // 要验证的字符串 String str "servicexsoftlab.net&q…...

【2023-3-29】JavaScript使用promise顺序调用函数并抛出异常

JavaScript使用promise顺序调用函数并抛出异常 场景 新建或者编辑时&#xff0c;一个页面中存在多个表单&#xff0c;每个表单都有单独进行表单验证。点击提交时&#xff0c;若有一个表单校验失败&#xff0c;则不能提交。 ps&#xff1a;为啥不放在一个表单中&#xff1f; (…...

Python实现GWO智能灰狼优化算法优化随机森林分类模型(RandomForestClassifier算法)项目实战

说明&#xff1a;这是一个机器学习实战项目&#xff08;附带数据代码文档视频讲解&#xff09;&#xff0c;如需数据代码文档视频讲解可以直接到文章最后获取。 1.项目背景 灰狼优化算法(GWO)&#xff0c;由澳大利亚格里菲斯大学学者 Mirjalili 等人于2014年提出来的一种群智能…...

从redis到epoll到mmap

redis为什么这么快&#xff1f; 比较容易答出的答案 1)纯粹的内存操作 2)单线程操作,不用考虑线程切换 其他优势 3)I/O 多路复用,使用epoll 4)Reactor 设计模式 I/O 多路复用有三种 select、poll、epoll select&#xff1a;使用数组存储轮询 poll&#xff1a;使用链表轮询 epo…...

STM32CubeMX快速构造工程模板(一)

STM32CubeMX作为一个免费开源的软件,能够可视化配置STM32或其他产品硬件资源,能过快速地构造工程模板,很是方便!!! 目录 STM32CubeMX快速构造工程模板 首先第一步,打开软件-点击按钮-输入型号-双击打开。...

Java Web中的ServletContext对象

目录 ServletContext对象 获取上下文初始化参数的相关方法 创建ServletContext对象 1&#xff09;通过 GenericServlet 提供的 getServletContext() 方法 2&#xff09;通过 ServletConfig 提供的 getServletContext() 方法 3&#xff09;通过 HttpSession 提供的 getServletCo…...

回归预测 | MATLAB实现PSO-RF粒子群算法优化随机森林多输入单输出回归预测

回归预测 | MATLAB实现PSO-RF粒子群算法优化随机森林多输入单输出回归预测 目录回归预测 | MATLAB实现PSO-RF粒子群算法优化随机森林多输入单输出回归预测效果一览基本介绍程序设计参考资料效果一览 基本介绍 MATLAB实现PSO-RF粒子群算法优化随机森林多输入单输出回归预测 粒子…...

在小公司工作3年,从事软件测试5年了,才发现自己还是处于“初级“水平,是不是该放弃....

毕业前三年&#xff0c;从早到晚&#xff0c;加班到深夜&#xff0c;一年又一年&#xff0c;直至刚入职场的首个黄金三年过年都去了&#xff0c;而职位却仍在原地踏步。尽管感觉自己努力过&#xff0c;但是实际上&#xff0c;自身的能力从没得到过多少提升。 所以在无数个夜晚…...

基于 OpenCV 与 Java 两个语言版本实现获取某一图片特定区域的颜色对比度

本文目录一、什么是对比度二、什么是颜色直方图三、如何通过RGB计算颜色对比度什么是HSV、Lab颜色空间四、OpenCV代码五、Java代码5.1 平滑处理5.2 完整代码一、什么是对比度 对比度是指图像中不同区域之间的明暗差异程度&#xff0c;它是图像质量中的重要指标之一。除了颜色对…...

Book:实战Java高并发程序设计(第二版)

实战Java高并发程序设计&#xff08;第二版&#xff09;为什么会有并行计算&#xff1f;并行计算需要回答的问题基本概念并发级别有哪些&#xff1f;Amdahl定律和Gustafson定律Java并发三特性进程和线程线程的生命周期Thread类run()与start()的区别为什么会有并行计算&#xff…...

LeetCode 831. Masking Personal Information【字符串,正则表达式】中等

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…...

递增三元组

[蓝桥杯 2018 省 B] 递增三元组 题目描述 给定三个整数数组 A[A1,A2,⋯,AN]A [A_1, A_2,\cdots, A_N]A[A1​,A2​,⋯,AN​]&#xff0c;B[B1,B2,⋯,BN]B [B_1, B_2,\cdots, B_N]B[B1​,B2​,⋯,BN​]&#xff0c;C[C1,C2,⋯,CN]C [C_1, C_2,\cdots,C_N]C[C1​,C2​,⋯,CN​…...

如何用滑模控制(SMC)解决机器人轨迹跟踪中的抖动问题?5个实战技巧分享

如何用滑模控制&#xff08;SMC&#xff09;解决机器人轨迹跟踪中的抖动问题&#xff1f;5个实战技巧分享 当机械臂在执行高精度焊接任务时&#xff0c;末端执行器突然出现5Hz的高频震颤——这种场景对工业机器人工程师而言绝不陌生。滑模控制&#xff08;SMC&#xff09;因其强…...

【含文档+PPT+源码】基于SSM框架的农产品销售平台的设计与实现

项目介绍本课程演示的是一款 基于SSM框架的农产品销售平台的设计与实现&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的 Java 学习者。1.包含&#xff1a;项目源码、项目文档、数据库脚本、软件工具等所有资料2.带你从零开始部署运行本套系统3.该项…...

暗黑破坏神2重制版智能辅助:自动化流程与效率提升完全指南

暗黑破坏神2重制版智能辅助&#xff1a;自动化流程与效率提升完全指南 【免费下载链接】botty D2R Pixel Bot 项目地址: https://gitcode.com/gh_mirrors/bo/botty 在《暗黑破坏神2&#xff1a;重制版》的冒险旅程中&#xff0c;你是否曾因重复刷怪、繁琐的装备拾取而感…...

cool-admin(midway版)前端表单验证:AsyncValidator与异步校验完整指南

cool-admin(midway版)前端表单验证&#xff1a;AsyncValidator与异步校验完整指南 【免费下载链接】cool-admin-midway &#x1f525; cool-admin(midway版)一个很酷的后台权限管理框架&#xff0c;模块化、插件化、CRUD极速开发&#xff0c;永久开源免费&#xff0c;基于midwa…...

Qwen3-14B GPU算力优化实践:显存占用降低28%的FlashAttention-2配置

Qwen3-14B GPU算力优化实践&#xff1a;显存占用降低28%的FlashAttention-2配置 1. 开箱即用的私有部署方案 对于想要快速部署Qwen3-14B大模型的企业和个人开发者来说&#xff0c;这个经过优化的私有部署镜像提供了完美的解决方案。它基于RTX 4090D 24GB显存显卡和CUDA 12.4环…...

手把手教你搭建PaddleOCR开发环境:从CUDA配置到模型验证

1. 环境准备&#xff1a;从零搭建PaddleOCR开发环境 最近在做一个票据识别的项目&#xff0c;需要用到OCR技术。对比了几种开源方案后&#xff0c;发现PaddleOCR不仅识别准确率高&#xff0c;而且对中文支持特别好。但在搭建环境时踩了不少坑&#xff0c;特别是CUDA和cuDNN的版…...

Android TTS开发避坑指南:为什么你的Google语音引擎播不出中文?从初始化到语音包管理的完整解决方案

Android TTS开发实战&#xff1a;解决Google语音引擎中文播报的7个关键问题 在移动应用开发中&#xff0c;文字转语音(TTS)功能正变得越来越重要。从无障碍辅助功能到语音导航、有声阅读&#xff0c;TTS技术为应用增添了更丰富的交互维度。然而&#xff0c;许多Android开发者在…...

万象视界灵坛代码实例:使用Gradio快速搭建像素风Web UI,零前端开发经验可用

万象视界灵坛代码实例&#xff1a;使用Gradio快速搭建像素风Web UI&#xff0c;零前端开发经验可用 1. 项目概述 万象视界灵坛是一款基于OpenAI CLIP模型的多模态智能感知平台&#xff0c;它将复杂的语义对齐功能包装在充满游戏感的像素风界面中。这个项目最大的特点是完全不…...

LumiPixel Canvas Quest教育应用:生成历史人物或文学角色形象辅助教学

LumiPixel Canvas Quest教育应用&#xff1a;生成历史人物或文学角色形象辅助教学 1. 教学场景中的视觉化挑战 历史课本上密密麻麻的文字描述和语文教材中抽象的人物描写&#xff0c;常常让学生难以形成直观印象。当讲到"秦始皇统一六国"时&#xff0c;学生脑海中可…...

保姆级教程:用SolidWorks URDF插件把你的机械设计变成Gazebo仿真模型

从SolidWorks到Gazebo&#xff1a;机械设计仿真全流程实战指南 机械工程师们常常面临一个挑战&#xff1a;如何在虚拟环境中快速验证设计方案的可行性&#xff1f;SolidWorks作为主流的三维设计工具&#xff0c;与Gazebo这一强大的机器人仿真平台结合&#xff0c;能够实现从概念…...