ThreadLocal详解
一、ThreadLocal简介
1、简介
ThreadLocal叫做线程变量,它是一个线程的本地变量,意味着这个变量是线程独有的,是不能与其他线程共享的。这样就可以避免资源竞争带来的多线程的问题。
即 ThreadLocal类用来提供线程内部的局部变量,不同的线程之间不会相互干扰。
ThreadLocal变量,即线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:
- 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这也是 ThreadLocal 命名的由来。
- 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。
注意:
- ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。
- ThreadLocal 变量通常被 private static修饰。当一个线程(Thread)结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。
2、ThreadLocal与Synchronized区别
ThreadLocal和Synchonized都用于解决多线程并发访问同一个资源对象的时候,可能就会出现线程不安全的问题。
- ThreadLocal是与一个线程绑定的本地变量,也就意味着这个变量是线程独有的,是不能与其他线程共享的。这样就可以避免资源竞争带来的多线程的问题。
- 加锁方式(synchronized、Lock) 用于在多个线程间通信时能够获得数据共享。
但是,ThreadLocal 这种解决多线程安全问题的方式与加锁方式(synchronized、Lock) 是有本质的区别。
两者区别如下:
(1)资源管理方面
- Synchronized通过加锁的方式,让多个线程之间逐一访问共享资源。
- ThreadLocal是每个线程都有一个资源副本,是不需要加锁的。
(2)实现方式方面
锁是通过时间换空间的做法。
Synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。ThreadLocal是通过空间换时间的做法。
ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。
3、使用场景
根据使用场景的不同,我们可以选择不同的技术手段,关键还是要看你的应用场景对于资源的管理是需要多线程之间共享还是单线程内部独享。
- 多线程之间共享资源:使用加锁方式。
- 单线程内部独享:使用 ThreadLocal变量。
ThreadLocal 适用于如下两种场景:
- 1)每个线程需要有自己单独的实例。
- 2)实例需要在多个方法中共享,但不希望被多线程共享。
二、ThreadLocal使用
使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
ThreadLocal类的常用方法:
- ThreadLocal threadLocal = new ThreadLocal();:创建ThreadLocal对象(即一个线程本地变量)。
- initialValue():返回此线程局部变量的当前线程的"初始值" 。
- set(T value):将此线程局部变量的当前线程副本中的值设置为value。。
- get():返回此线程局部变量的当前线程副本中的值 。
- remove():移除当前线程绑定的局部变量,该方法可以帮助JVM进行GC。
1、示例1
public class ThreadLocalUseDemo1 {private static ThreadLocal<String> threadLocal1 = new ThreadLocal<>();private static ThreadLocal<Integer> threadLocal2 = new ThreadLocal<>();/*** 运行 count个线程,每个线程持有自己独有的 String类型编号*/public void startThreadArray(int count) {Thread[] runs = new Thread[count];for (int i = 0; i < runs.length; i++) {// 赋值编号idnew ThreadDemo1(i).start();}}/*** 线程类:*/public static class ThreadDemo1 extends Thread {/*** 编号id*/private int codeId;public ThreadDemo1(int codeId) {this.codeId = codeId;}@Overridepublic void run() {String threadName = Thread.currentThread().getName();threadLocal1.set("threadLocal1赋值,线程_" + codeId);if (codeId == 2) {//如果是线程2,设置 threadLocal2变量,值乘以5threadLocal2.set(codeId * 5);}System.out.println(threadName + " -》 " + threadLocal1.get());System.out.println(threadName + " -》 " + threadLocal2.get());// 使用完移除,help GCthreadLocal1.remove();threadLocal2.remove();}}public static void main(String[] args) {ThreadLocalUseDemo1 useDemo = new ThreadLocalUseDemo1();// 启动3个线程useDemo.startThreadArray(3);}}

从示例1中可以看到,每个线程分别获取了自己线程存放的变量,他们之间变量的获取并不会错乱。
2、示例2
public class ThreadLocalUseDemo2 {//public static ThreadLocalUseDemo2.Number number = new ThreadLocalUseDemo2.Number(0);/*** 初始化 num值。使用时,先通过get方法获取。*/public static ThreadLocal<ThreadLocalUseDemo2.Number> threadLocalValue = new ThreadLocal<ThreadLocalUseDemo2.Number>() {@Overrideprotected Number initialValue() {return new Number(0);}};/*** 数据类*/private static class Number {public Number(int num) {this.num = num;}private int num;public int getNum() {return num;}public void setNum(int num) {this.num = num;}@Overridepublic String toString() {return "Number [num=" + num + "]";}}/*** 线程类:*/public static class ThreadDemo2 extends Thread {@Overridepublic void run() {// 如果没有初始化,注意NPE。// static修饰的 number时,注释掉这句Number number = threadLocalValue.get();//每个线程计数加随机数Random r = new Random();number.setNum(number.getNum() + r.nextInt(100));//将其存储到ThreadLocal中threadLocalValue.set(number);try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}//打印保存的随机值System.out.println(Thread.currentThread().getName() + " -》 " + threadLocalValue.get().getNum());threadLocalValue.remove();System.out.println(Thread.currentThread().getName() + " remove方法之后 -》 " + threadLocalValue.get().getNum());}}public static void main(String[] args) {// 启动5个线程for (int i = 0; i < 5; i++) {new ThreadDemo2().start();}}
}

从示例2中可以看到,每个线程可以通过 initialValue方法初始化变量值 。如果使用 public static ThreadLocalUseDemo2.Number number赋值,会导致数值一样。因为是 static修饰的,所有线程都指向同一个对象。
三、ThreadLocal源码分析
查看 ThreadLocal类的常用方法源码。
1、set()方法
查看 set方法。
public void set(T value) {// 1、获取当前线程Thread t = Thread.currentThread();// 2、获取线程中的属性 threadLocalMap//如果threadLocalMap 不为空,则直接更新要保存的变量值,否则创建threadLocalMap,并赋值ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);else// 初始化thradLocalMap 并赋值createMap(t, value);}
1.1 ThreadLocalMap简介
ThreadLocalMap是 ThreadLocal的内部静态类,其实 ThreadLocalMap是个标准的 Map实现,内部有一个元素类型为 Entry 的数组,用以存放线程可能需要的多个副本变量。
即 ThreadLocalMap的构成主要是用 Entry来保存数据 ,而且还是继承的弱引用。在 Entry内部使用 ThreadLocal作为key,使用我们设置的value作为value。
查看 ThreadLocalMap类。
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 getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.get() == key)return e;elsereturn getEntryAfterMiss(key, i, e);}private void set(ThreadLocal<?> key, Object value) {// We don't use a fast path as with get() because it is at// least as common to use set() to create new entries as// it is to replace existing ones, in which case, a fast// path would fail more often than not.Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);for (Entry e = tab[i];e != null;// hash冲突时,使用了开放定址法(线性探测再散列即依次向后查找)。e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();if (k == key) {e.value = value;return;}if (k == null) {replaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();}private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);}// ...}
可以看到 Entry 内部静态类,它继承了 WeakReference(弱引用),一个key是 ThreadLocal<?>类型,一个value是 Object 类型的值。
- getEntry 方法:是获取某个 ThreadLocal 对应的值。
- set 方法:是更新或赋值相应的 ThreadLocal对应的值。
1.2 初始化ThreadLocalMap
接着查看 ThreadLocal.createMap方法。
void createMap(Thread t, T firstValue) {// 创建 ThreadLocalMapt.threadLocals = new ThreadLocalMap(this, firstValue);}//ThreadLocalMap 构造方法ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY);}
注意:t.threadLocals 的代表的字段信息。

通过这里我们发现,每个线程都拥有一个 ThreadLocalMap类用 Entry存放保存数据,在 Entry内部使用 ThreadLocal作为key,使用我们设置的value作为value。从而保证每个线程内部的局部变量副本相互干扰。
2、get方法
查看 get方法。
public T get() {//1、获取当前线程Thread t = Thread.currentThread();//2、获取当前线程的ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null) {//3.1、获取threalLocalMap中存储的值ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}//3.2 如果是数据为null,则初始化,初始化的结果,TheralLocalMap中存放key值为threadLocal,值为nullreturn 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;}
get 方法,其实就是拿到每个线程独有的 ThreadLocalMap。
然后再用 ThreadLocal 的当前实例,拿到 Map 中的相应的 Entry,然后就可以拿到相应的值返回出去。如果 Map 为空,还会先进行 map 的创建,初始化等工作并返回null。
3、remove方法
查看 remove方法。
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);}/*** Remove the entry for key.*/private void remove(ThreadLocal<?> key) {Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {if (e.get() == key) {e.clear();expungeStaleEntry(i);return;}}}
remove方法,直接将 ThrealLocal 对应的值从当前相差 Thread中的 ThreadLocalMap中删除。
如果使用完不删除的话,就会涉及到内存泄露的问题。
这是因为 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用。
弱引用的特点是:如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。
如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样一来 ThreadLocalMap中使用这个 ThreadLocal 的 key 也会被清理掉。但是,value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value。因为,一般定义ThreadLocal 变量通常被 private static修饰。
参考文章:
- ThreadLocal源码解析:https://blog.csdn.net/qq_26470817/article/details/124993311
– 求知若饥,虚心若愚。
相关文章:
ThreadLocal详解
一、ThreadLocal简介 1、简介 ThreadLocal叫做线程变量,它是一个线程的本地变量,意味着这个变量是线程独有的,是不能与其他线程共享的。这样就可以避免资源竞争带来的多线程的问题。 即 ThreadLocal类用来提供线程内部的局部变量࿰…...
利用Cookie劫持+HTML注入进行钓鱼攻击
目录 HTML注入和cookie劫持: 发现漏洞 实际利用 来源 HTML注入和cookie劫持: HTML注入漏洞一般是由于在用户能够控制的输入点上,由于缺乏安全过滤,导致攻击者能将任意HTML代码注入网页。此类漏洞可能会引起许多后续攻击&#…...
【接口汇总】常用免费的API
短信API 短信验证码:可用于登录、注册、找回密码、支付认证等等应用场景。支持三大运营商,3秒可达,99.99%到达率,支持大容量高并发。 通知短信:当您需要快速通知用户时,通知短信是最快捷有效的…...
数字信号处理知识点
数字信号处理知识点1 频谱图中,横坐标取值范围的含义2 MATLAB常用函数2.1 波形产生2.2 滤波器分析2.3 滤波器实现2.4 线性系统变换2.5 滤波器设计2.5.1 FIR滤波器2.5.2 IIR滤波器2.6 Transforms(变换)2.7 统计信号处理和谱分析2.8 Windows(窗函数)2.9 Parametric Mo…...
计算机网络第八版——第三章课后题答案(超详细)
第三章 该答案为博主在网络上整理,排版不易,希望大家多多点赞支持。后续将会持续更新(可以给博主点个关注~ 第一章 答案 第二章 答案 【3-01】数据链路(即逻辑链路)与链路(即物理链路)有何区…...
九龙证券|磷酸亚铁锂是什么?磷酸亚铁锂的特点和性能介绍
磷酸亚铁锂是一种新式锂离子电池电极资料,化学式:LiFePO4,磷酸亚铁锂为近来新开发的锂离子电池电极资料,首要用于动力锂离子电池,作为正极活性物质运用,人们习气也称其为磷酸铁锂。 磷酸亚铁锂的特色和功能…...
3D目标检测(二)—— 直接处理点云的3D目标检测网络VoteNet、H3DNet
前言上次介绍了基于Point-Based方法处理点云的模块,3D目标检测(一)—— 基于Point-Based方法的PointNet点云处理系列,其中相关的模块则是构成本次要介绍的,直接在点云的基础上进行3D目标检测网络的基础。VoteNet对于直接在点云上预…...
Java学习-IO流-常用工具包(hutool)
Java学习-IO流-常用工具包(hutool) hutool工具包 DateUtil:日期时间工具类 TImeInterval:计时器工具类 StrUtil:字符串工具类 HexUtil:16进制工具类 HashUtil:Hash算法类 ObjectUtil࿱…...
【LeetCode】1. 两数之和
题目链接:https://leetcode.cn/problems/two-sum/ 📕题目要求: 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入…...
【数值模型环境搭建】Intel编译器安装
Intel编译器在数值模型编译中被广泛使用,它有一个很好的地方是自带Mpich,不需要额外安装。本文介绍Intel2018.1.163版本的安装。 1、安装包获取 Intel编译器可从官网下载下载: https://www.intel.cn/content/www/cn/zh/homepage.html 或者…...
操作VMware vCenter Converter 实现物理机迁移到虚拟机
实验目的:熟练VMware虚拟化项目中,物理机向ESXI5迁移操作过程。 1、打开VMwarevCenterConverterStandalone5.0软件,按“转换计算机”。 2、选择“已打开电源的计算机”。并输入远程要连接迁移物理机IP地址,登录帐户和密码。 然后…...
hutool XML反序列化漏洞(CVE-2023-24162)
漏洞简介 Hutool 中的XmlUtil.readObjectFromXml方法直接封装调用XMLDecoder.readObject解析xml数据,当使用 readObjectFromXml 去处理恶意的 XML 字符串时会造成任意代码执行。 漏洞复现 我们在 maven 仓库中查找 Hutool https://mvnrepository.com/search?…...
Java简单认识泛型——图文详解
写在开头:想必大家和博主一样,在以往学习JavaSE的语法中,遇到了一个陌生的词——泛型,博主当时很好奇,什么是泛型呢?即使是学完了JavaSE,这个问题都没有解决,只能在百度查阅了解关于泛型的一些皮…...
AcWing171.送礼物
题目描述 达达帮翰翰给女生送礼物,翰翰一共准备了NNN 个礼物,其中第 iii 个礼物的重量是 G[i]G[i]G[i]。 达达的力气很大,他一次可以搬动重量之和不超过 WWW 的任意多个物品。 达达希望一次搬掉尽量重的一些物品,请你告诉达达在…...
领域驱动设计-架构篇
目录 1、软件架构概述 1.1 软件架构概念 1.2 软件架构分类 1.3 软件架构模式 1.4 软件架构风格 2、领域驱动软件架构 2.1 架构风格 六边行架构(领域驱动设计首选) 为什么选择REST架构 松耦合 可伸缩性 易用性 约束性 2.2 架构模型 命令和…...
docker安装kafka
前言最近在用kafka做项目,所以本地搭建下kafka,但是又嫌java安装和安装kafka太麻烦,所以想到用docker来部署。镜像wurstmeister/kafka维护较为频繁的一个Kafka镜像。只包含了Kafka,因此需要另行提供ZooKeeper,推荐使用…...
Selenium4+Python3系列(十一) - Page Factory设计模式
写在前面: Page Object模式,目的是将元素定位和元素操作分层,只接触测试内容,不写基础内容,便于后续对自动化测试用例体系的维护,这是中心思想,也是核心。 那么我们继续将简洁延续,…...
C++基础知识【4】函数及参数
目录 一、函数的基本概念 1.1、构成 1.2、声明和定义 1.3、函数的调用 二、参数 2.1、形参和实参 2.2、参数的传递 传值 传引用 传指针 三、C函数的一些新特性 3.1、Lambda表达式 3.2、右值引用 3.3、默认参数 3.4、变长参数模板 3.5、constexpr函数 3.6、noex…...
约瑟夫森磁效应
电流与波函数的相位有直接的关系,可得约瑟夫森结的电流为 IIcsinϕ\begin{align} II_c sin\phi \end{align} IIcsinϕ 式中,IcI_cIc为临界电流,相位差为ϕϕ2−ϕ1\phi\phi_2-\phi_1ϕϕ2−ϕ1。 根据磁矢势A的定义,B…...
什么是L1和L2正则化,以及它们有什么区别
一、L1和L2正则化是什么? 在防止过拟合的方法中有L1正则化和L2正则化,L1和L2是正则化项,又叫做惩罚项,是为了限制模型的参数,防止模型过拟合而加在损失函数后面的一项。 在二维的情况下,黄色的部分是L2和…...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...
8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
镜像里切换为普通用户
如果你登录远程虚拟机默认就是 root 用户,但你不希望用 root 权限运行 ns-3(这是对的,ns3 工具会拒绝 root),你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案:创建非 roo…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
Python 实现 Web 静态服务器(HTTP 协议)
目录 一、在本地启动 HTTP 服务器1. Windows 下安装 node.js1)下载安装包2)配置环境变量3)安装镜像4)node.js 的常用命令 2. 安装 http-server 服务3. 使用 http-server 开启服务1)使用 http-server2)详解 …...
云安全与网络安全:核心区别与协同作用解析
在数字化转型的浪潮中,云安全与网络安全作为信息安全的两大支柱,常被混淆但本质不同。本文将从概念、责任分工、技术手段、威胁类型等维度深入解析两者的差异,并探讨它们的协同作用。 一、核心区别 定义与范围 网络安全:聚焦于保…...
聚六亚甲基单胍盐酸盐市场深度解析:现状、挑战与机遇
根据 QYResearch 发布的市场报告显示,全球市场规模预计在 2031 年达到 9848 万美元,2025 - 2031 年期间年复合增长率(CAGR)为 3.7%。在竞争格局上,市场集中度较高,2024 年全球前十强厂商占据约 74.0% 的市场…...
基于小程序老人监护管理系统源码数据库文档
摘 要 近年来,随着我国人口老龄化问题日益严重,独居和居住养老机构的的老年人数量越来越多。而随着老年人数量的逐步增长,随之而来的是日益突出的老年人问题,尤其是老年人的健康问题,尤其是老年人产生健康问题后&…...
