深挖 ThreadLocal 底层原理?它有什么用?学会之后手撕面试官
目录
1. ThreadLocal 的主要功能?
2. ThreadLocal 代码举例
3. ThreadLocal 源码分析
3.1 ThreadLocal 的 get 方法源码解析
3.2 ThreadLocal 的 set 方法源码解析
3.3 ThreadLocal 的 createMap 方法源码解析
3.4 ThreadLocal 的 set 方法总结
4. 为什么Entry要使用弱引用指向ThreadLocal
5. 如果将 tl 设置为 null,一定要将Map中的对应记录remove
6. 线程池中的线程使用完毕归还之前清除Map
7. ThreadLocal 广泛应用于线程池
1. ThreadLocal 的主要功能?
关于 ThreadLocal 的核心功能我大致总结了以下三点
(1)线程并发:ThreadLocal 更多应用于多线程并发的场景下;
(2)传递数据:我们可以通过 ThreadLocal 在同一个线程,不同组件中传递公共变量;
(3)线程隔离:每个线程的变量都是独立隔离的,不会互相影响;
或许有些同学听完之后会有些疑惑,我来举一个简单的场景,如下图所示,假定现在有一个线程,里面有一个变量X,最先执行a方法,a方法中调用了b方法,b方法中调用了c方法,c方法又调用了d方法,层层调用,我现在问,随着调用层次的加深,层次深得方法还能获取这个变量X吗?
答案是不一定。
有些同学可能会说,我把这个参数每一层方法都传递一次不就可以了吗?这还真不一定行,如果中间你的方法调用了某些第三方库,或者做了一些别的业务逻辑,变量X就不一定能继续传递下去了;
还有的同学会说,这个简单,我把变量X设置为 static 静态的不就行了吗,所有人共享,一下子就获取到了。哎,这个也不行哦,因为我现在只是举了一个线程,如果这个变量X是一个共享资源,会有多个线程会操作它,此时它就是线程不安全的,所以设置为 static 也是不行的。
既然如此,那该怎么做呢?
这里就要用到我们接下来要说的 ThreadLocal,通过使用 ThreadLocal,我们就可以使变量X在多线程并发的情况下也能线程安全的使用变量X并且不需要上锁,而且做到各个线程之间相互隔离,达到在同一个线程中任何时刻都能够获取到变量X的效果。
2. ThreadLocal 代码举例
如下代码,我都做了一些注释,应该不难理解,所以就不做过多赘述了。
public class WeakReferenceTest {public static void main(String[] args) {// 定义一个 ThreadLocal 全局变量 tlThreadLocal tl = new ThreadLocal();// 创建一个线程new Thread(() -> {// 向 tl 对象中存放一个对象tl.set(new WeakReferenceTest());// 存放对象之后获取,看能否获取成功System.out.println(tl.get()+"存放当前类对象的线程获取存放的类对象");}).start();// 再创建一个新的线程new Thread(() -> {// 让该线程睡1秒,保证钱一个线程一定存储对象成功try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}// get 取出上一个线程存放的类对象,看啊可能能否获取到System.out.println(tl.get()+"非存放当前类对象的线程获取存放的类对象");}).start();}
}
然后我们运行上述方法,可以在控制台中得到如下图所示的结果,第一个线程自己向 ThreadLocal 对象中存放创建的类对象时,能够获取得到;但是第二个线程从 ThreadLocal 对象中获取第一个线程存放的类对象时,获取不到,得到的是null。但是我们明明把 ThreadLocal 对象 tl 定义成全局变量了啊,怎么会拿不到呢?
这就是 ThreadLocal 对象特有的属性,从这个例子中,应该也不难看出,ThreadLocal 对象对于线程有自带的隔离性,就算是多个线程共用一个 ThreadLocal 对象,但他们之间存取并不会互相影响。
3. ThreadLocal 源码分析
通过刚才的代码举例演示,同学们也应该简单了解了 ThreadLocal 的特性,那么它是如何做到多县城隔离性的呢?这就要通过阅读它的源码了解底层原理。
3.1 ThreadLocal 的 get 方法源码解析
我们看一下 ThreadLocal 的 set 方法的源码如下所示,方法中它获取了当前线程对象,并用 t 来接收,接受之后调用了 getMap 方法,我们点击跟进查看一下 getMap 方法的源码
如下所示,该方法在 ThreadLocal 类中,找到了 getMap 方法,该方法源码注释最后一行也说了,方法返回值是一个 Map 集合。
方法 return t.threadLocals ,我们点击查看一下它到底是什么东西,如下所示
此时可以看到,我们跳转到了 Thread 类中,源码中 threadLocals 默认为null,它的类型是 ThreadLocalMap,我们点击它查看一下,
此时又跳转到了 ThreadLocal 类中,ThreadLocalMap 是 ThreadLocal 类中的一个内部类,它维护了一个 Map 数组,INITIAL_CAPACITY 初始容量为16,
说明了在 Java 底层,每个Thread 线程对象底层都维护了一个 Map 数组
3.2 ThreadLocal 的 set 方法源码解析
然后下面对 Map 做判断,如果 Map 集合不为空,说明已经存在,则将(this,value)存入其中,认真分析,此时的 this 是什么,是方法的调用者,也就是说 ThreadLocal 的对象 tl 是 Map 集合的 key,value值就是我们方法传入的 value值;
点击跟如查看 set 方法的源码,这里它有一个 Entry,Entry最终存放到了 Map 集合中去,其实一个 Entry 对象就是 Map 集合中的一条记录,Entry 对象内部就是KV键值对,K就是 ThreadLocal 对象,V 就是方法传递过来的值。
下面我们点击 Entry 查看,发现 Entry 是 ThreadLocalMap 类中的内部了,并且该类继承了 WeakReference<>弱引用类, 现在我们发现,ThreadLocal 竟然与弱引用有所关联,并且调用了父类super的方法,所以也就是说他创建的对象引用类型是弱引用类型。
这里补充一点,如果有同学不懂什么是弱引用,可以去看我的另一篇文章,对强引用,弱引用,软引用,虚引用均有所介绍。建议先搞明白什么是弱引用再来学习ThreadLocal,有助于更深入的理解 ThreadLocal 的原理。
强引用,弱引用,软引用,虚引用它们有什么区别?你知道吗?-CSDN博客https://blog.csdn.net/m0_70325779/article/details/133268853?spm=1001.2014.3001.5501
3.3 ThreadLocal 的 createMap 方法源码解析
接着刚才的 3.1 ,如果 Map 集合为空,说明当前线程应该是首次设置 ThreadLocal 属性,所以需要调用 createMap 方法先将当前线程的 Map 集合创建出来,然后再将 value 值设置其中。流程与 if 语句中差不多,都是若引用类型对象。
createMap 源码如下所示,这里它创建了一个 ThreadLocalMap 集合
点击 ThreadLocal 的带参构造,即可跳转至如下界面,这里底层 new 了一个大小为 16 的 Map 集合
3.4 ThreadLocal 的 set 方法总结
经过上面三点的说明,我画了一个的简化图,
(1)程序中我们 new 一个 ThreadLocal 对象用 tl 接收,也可以 new 多个;
(2)每个线程内部都有一个 tls 属性指向独属于自己的那个 Map 集合;
(3)当我们调用 tl 对象 set 方法的时候,实际上底层会创建一个 Entry 键值对对象,K是 set 方法的调用者 tl,V 则是我们要保存的 value 值;
(4)在进行 set 存放操作的时候底层创建的 Entry 对象则是弱引用类型,存放在 Map 集合中
4. 为什么Entry要使用弱引用指向ThreadLocal
经过上面的分析,我们知道了,Map 集合中 Entry 的 key 采用弱引用的方式指向 ThreadLcoal 对象,但是为什么要采用弱引用呢?
答案是因为弱引用可以巧妙地解决 ThreadLocal 带来的内存泄漏问题。
我来给大家说明一下,假设现在我的程序中使用 ThreadLocal tl = new ThreadLocal() 定义了一个 ThreadLocal 对象,并且在当线程执行了 tl.set 将 tl 存储到了当前线程中,随着程序的运行,我们不再需要 ThreadLocal 对象了,于是我们将 tl 设置为空,我们希望垃圾回收期将 ThreadLocal 对象回收。
但是,垃圾回收器真的能将 ThreadLocal 对象回收吗?
如果 Entry 对象中的 key 采用了强引用的方式指向 ThreadLocal 对象,即使将 tl 设置为 null,ThreadLocal 对象也不能被回收,因为 Entry 中的 key 仍然指向它,所以 ThreadLocal 就会作为垃圾一直存在于内存中不能被垃圾回收器回收,除非当前线程结束运行,Map 集合对象被回收,ThreadLocal 对象也跟着被回收。但在实际业务场景中,有些线程会一直运行,那么 ThreadLocal 也会一直存在于内存中,如果有很多的 ThreadLocal 对象都是这样不能被回收存在于内存中,就会造成内存泄漏。
如果 Entry 中的 key 以弱引用的方式指向 ThreadLocal 对象,当 tl 对象设置为 null 时,ThreadLocal 对象就只剩下弱引用指向它,当垃圾回收器遇到 ThreadLocal 对象时,就可以将它回收,避免了内存泄露。
5. 如果将 tl 设置为 null,一定要将Map中的对应记录remove
上面说到了 Entry 对象中的 key 采用弱引用的方式解决了 ThreadLocal 可能产生的内存泄漏问题,但是,产生内存泄露的不只是 ThreadLocal。
各位同学现在仔细想想,还是接着上面的第四点,我将 tl 设置为 null,tl 与 ThreadLocal 对象之间的引用就会断开,那么此时线程内部的 Map 集合保存的 ThreadLocal 那条记录的 key 也会变成 null,如下图所示。
那么现在我们就需要注意一点啦!!!
Map 集合中 Entry 的 key 变成了 null,那么此时对应的 value 值我们是访问不到的,既然访问不到,也就没有用了,value值也会变成垃圾对象,需要被垃圾回收器回收。所以如果在程序中将 ThreadLocal 对象设置为 null 的时候,同时也需要将 Map 集合中 key 为 null 的记录全部 remove,避免 value 值无法被回收造成内存泄露。
6. 线程池中的线程使用完毕归还之前清除Map
在实际开发的过程中,我们往往不会去创建新的线程,而是使用线程池中的线程,在使用完毕之后再归还到线程池,避免线程的重复创建和销毁。
线程池的使用也随之带来了另一个问题——线程在归还到线程池之前一定要将 Map 集合清除。
原因很简单,假设现在我从线程池中拿到了一个线程来使用,并在使用的过程中向 map 集合中存放了一些 ThreadLocal 对象,我在使用完线程之后不将 Map 集合清除直接归还到了线程池中。当下次再有其它业务使用线程时,再向 Map 集合中存放 ThreadLocal 对象,如果 key 相同,就会把原来的 ThreadLocal 对象覆盖;还有一种情况就是直接从 Map 集合中取 ThreadLocal 对象,那么取出来的就是之前的,很有可能会产生错误,对业务造成影响。
7. ThreadLocal 广泛应用于线程池
如果你有阅读过线程池的源码,你就会发现线程池中广泛应用到了 ThreadLocal,并且线程池中的线程完成任务之后,首先就会清理线程内部的 Map 集合,防止对下一次的线程操作产生影响。
而且 Spring 框架中的 @Transaction 事务注解;
线程池中的部分源码,都应用到了ThreadLocal;
除此之外,一些集合中为了防止造成内存泄漏,也会加入 ThreadLocal 。
ThreadLocal 也是面试中可能会问到的一个比较重要的知识点,如果能看懂本篇文章,应对面试可以说绰绰有余了。
相关文章:

深挖 ThreadLocal 底层原理?它有什么用?学会之后手撕面试官
目录 1. ThreadLocal 的主要功能? 2. ThreadLocal 代码举例 3. ThreadLocal 源码分析 3.1 ThreadLocal 的 get 方法源码解析 3.2 ThreadLocal 的 set 方法源码解析 3.3 ThreadLocal 的 createMap 方法源码解析 3.4 ThreadLocal 的 set 方法总结 4. 为什么En…...
sort()排序函数(c++)
文章目录 sort()排序函数(c)一、原理二、使用方法(一)头文件(二)使用语法1.方式一(默认)2.方式二:定义升序或降序3.方式三:自定义 sort()排序函数(…...

如何评估测试用例的优先级?
评估测试用例的优先级,有助于我们及早发现和解决可能对系统稳定性和功能完整性产生重大影响的问题,助于提高测试质量,提高用户满意度。 如果没有做好测试用例的优先级评估,往往容易造成对系统关键功能和高风险场景测试的忽略&…...

510758-28-8,用于标记蛋白质和酶的配体TBTA
产品简介:Tris(benzyltriazolylmethyl)amine (TBTA)是一种配体,能作为生化工具用于标记蛋白质和酶。 CAS号:510758-28-8 中文名:三[(1-苄基-1H-1,2,3-三唑-4-基)甲基]胺 英文名:TBTA 化学式:C30H30N10…...
Jtti:云服务器ftp不能访问端口如何解决
如果您的云服务器上的FTP服务无法访问端口,可能有多种原因导致这种情况。以下是一些可能的解决方法: 检查FTP服务状态: 首先,请确保您的FTP服务器正在运行。您可以使用以下命令来检查FTP服务器的状态,具体命令可能因FT…...

云服务器租用价格表概览_阿里云腾讯云华为云
云服务器租用价格多少钱一年?阿腾云分享阿里云、腾讯云和华为云的云服务器租用价格表:阿里云2核2G服务器108元一年起、腾讯云2核2G3M带宽轻量服务器95元一年、华为云2核2G3M云耀L实例89元一年起,阿腾云分享更多关于云服务器租用价格明细&…...
E. Iva Pav -前缀和 + 二分 +位运算
题面 分析: 赛时一直纠结于与运算前缀和不可逆,导致没有思路,但是发现行不通并没有及时思考别的解决办法导致一条路走到黑,阻碍了自己的思维,在今年的网络赛赛时也是一样,行不通的时候就没心思去重新想其…...

新手学习:ArcGIS对shp文件裁剪
新手学习:ArcGIS对SHP文件裁剪 新手学习 记录每个步骤,因为有很多控件可能刚开始还不熟悉,根本不知道在哪里,所以写的比较详细。 1.添加要裁剪的shp文件 2.查看shp文件的地理坐标系 双击shp文件,就可以查看shp文件的…...

Java 设计模式——抽象工厂模式
目录 1.概念2.结构3.实现4.优缺点5.使用场景6.模式扩展7.JDK源码解析——Collection.iterator方法 1.概念 (1)Java 设计模式——工厂方法模式中考虑的是一类产品的生产,如畜牧场只养动物、电视机厂只生产电视机等。这些工厂只生产同种类产品…...

如何使用ChatGPT构建一个Web应用程序?
围绕ChatGPT的最大卖点之一是它可以成为一种有效的编程工具。其想法是这样的:你用自然语言描述需求,该聊天机器人生成满足该需求的代码。但是ChatGPT在这方面到底有多好呢? 还有什么比亲自测试一下更好的方法呢?我们让ChatGPT从头…...

关闭手机广告的步骤
关闭手机广告的步骤 小米 1.设置→小米账号→声明与条款→系统广告→系统工具广告→关闭 2.设置→应用设置→应用管理→右上角三个点→设置→关闭“应用升级提醒”&“资源推荐” 3.桌面左滑打开负一屏→划到底部→设置→服务管理→选择关闭项目 4.桌面→打开任意文件夹…...

【Verilog 教程】6.6Verilog 仿真激励
关键词:testbench,仿真,文件读写 Verilog 代码设计完成后,还需要进行重要的步骤,即逻辑功能仿真。仿真激励文件称之为 testbench,放在各设计模块的顶层,以便对模块进行系统性的例化调用进行仿真…...

Win/Mac版Scitools Understand教育版申请
这里写目录标题 前言教育版申请流程教育账号申请 前言 上篇文章为大家介绍了Scitools Understand软件,通过领取的反馈来看有很多朋友都想用这个软件,但是我的网盘里只存了windows的pojie版,没有mac版的,我没有去网上找相关的资源…...

第十四届蓝桥杯大赛软件赛决赛 C/C++ 大学 B 组 试题 C: 班级活动
[蓝桥杯 2023 国 B] 班级活动 【问题描述】 小明的老师准备组织一次班级活动。班上一共有 n n n 名( n n n 为偶数)同学,老师想把所有的同学进行分组,每两名同学一组。为了公平,老师给每名同学随机分配了一个 n n …...
YOLOv8改进新颖的Gather-and-Distribute机制,低阶高阶新颖融合,增强了多尺度特征融合能力,实现了延迟和准确性的理想平衡
💡本篇内容:YOLOv8改进新颖的Gather-and-Distribute机制,低阶高阶新颖融合,增强了多尺度特征融合能力,实现了延迟和准确性的理想平衡 💡🚀🚀🚀本博客 改进源代码改进 适用于 YOLOv8 按步骤操作运行改进后的代码即可 💡本文提出改进 原创 方式:二次创新,YOL…...

面试算法13:二维子矩阵的数字之和
题目 输入一个二维矩阵,如何计算给定左上角坐标和右下角坐标的子矩阵的数字之和?对于同一个二维矩阵,计算子矩阵的数字之和的函数可能由于输入不同的坐标而被反复调用多次。例如,输入图2.1中的二维矩阵,以及左上角坐标…...
Vue安装插件时候中遇到冲突依赖解决方案
错误如下: npm ERR! code ERESOLVE npm ERR! ERESOLVE could not resolve npm ERR! npm ERR! While resolving: vue/eslint-config-standard6.1.0 npm ERR! Found: eslint-plugin-vue8.7.1 npm ERR! node_modules/eslint-plugin-vue npm ERR! dev eslint-pl…...

realloc函数应用IO泄露体验
本题主要介绍realloc函数,平时我们使用realloc最多便是在打malloc_hook–>onegadget的时候,使用realloc_hook调整onegadget的栈帧,从而getshell。 在realloc函数中,也能像malloc一样创建堆,并且比malloc麻烦一些&a…...
(c语言)野指针
#include<stdio.h> //野指针 int* test() { int a 10; return &a; } int main() { //野指针一: int* p; *p 10; //非法访问内存 //p没有初始化,就意味着没有明确的指向 //一个局部变量不初始化的话ÿ…...

【Git】轻松学会 Git(一):掌握 Git 的基本操作
文章目录 前言一、创建 Git 本地仓库1.1 什么是仓库1.2 创建本地仓库1.3 .git 目录结构 二、配置 Git三、认识 Git 的工作区、暂存区和版本库3.1 什么是 Git 的工作区、暂存区和版本库3.2 工作区、暂存区和版本库之间的关系 四、添加文件4.1 添加文件到暂存区和版本库中的命令4…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
DockerHub与私有镜像仓库在容器化中的应用与管理
哈喽,大家好,我是左手python! Docker Hub的应用与管理 Docker Hub的基本概念与使用方法 Docker Hub是Docker官方提供的一个公共镜像仓库,用户可以在其中找到各种操作系统、软件和应用的镜像。开发者可以通过Docker Hub轻松获取所…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...

【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...

高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
django filter 统计数量 按属性去重
在Django中,如果你想要根据某个属性对查询集进行去重并统计数量,你可以使用values()方法配合annotate()方法来实现。这里有两种常见的方法来完成这个需求: 方法1:使用annotate()和Count 假设你有一个模型Item,并且你想…...

智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
Python ROS2【机器人中间件框架】 简介
销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...
Linux系统部署KES
1、安装准备 1.版本说明V008R006C009B0014 V008:是version产品的大版本。 R006:是release产品特性版本。 C009:是通用版 B0014:是build开发过程中的构建版本2.硬件要求 #安全版和企业版 内存:1GB 以上 硬盘…...