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和…...
场景式消费激发春日经济,这些电商品类迎来消费热潮
春日越临近,商机越浓郁。随着气温渐升,春日经济已经潜伏在大众身边。“春菜”、“春装”、“春游”、“春季养生”等春日场景式消费走热。 下面,鲸参谋为大家盘点几个与春日经济紧密相关的行业。 •春日仪式之春游踏青 ——户外装备全面开花…...
[2.1.4]进程管理——进程通信
文章目录第二章 进程管理进程通信(IPC)为什么进程通信需要操作系统支持?(一)共享存储(1)基于存储区的共享(2)基于数据结构的共享(二)消息传递什么…...
ChatGPT也有犯晕的时候
前面测试 ChatGPT 进行写代码、优化代码、解释代码、一般问答都表现的很好。偷个懒,用ChatGPT 帮我写段生物信息代码如果 ChatGPT 给出的的代码不太完善,如何请他一步步改好?代码看不懂?ChatGPT 帮你解释,详细到爆&…...
机器学习与目标检测作业:连通块算法
机器学习与目标检测作业:连通块算法一、连通块算法题目描述二、连通块算法文件结构三、连通块算法程序编写3.1、连通块算法conBlock.h头文件内容3.2、conBlock.cpp源文件内容3.3.3、mian.h头文件内容3.3.4、main.cpp源文件内容如下四、连通块算法程序运行结果一、连…...
HBase基础 --- 增删查改
目录 创建表 查看指定表全名空间中的表 查看表描述 禁用/启用 查看禁用/启动状态 删除表 新增列族 删除列族 更改列族存储版本的限制 增加数据 根据条件查询 查看指定列中不同版本的数据 删除指定列族下的指定列 删除指定行 全表扫描 全表扫描指定列族…...
如何基于AI智能视频技术实现公园景区的人流量实时统计?
一、方案背景春暖花开的季节来临,外出旅游的人群也越来越多。无论是景区、公园、博物馆、步行街等场所,客流超载非常大,给游客带来的体验较差,同时也存在安全隐患。当前景区面临的管理痛点包括:客流信息查询难…...
【JavaWeb】Servlet详解
文章目录1. 前置知识2.servlet生命周期2.1 默认情况下,服务器启动时,servlet对象并没有被创建2.2 用户执行一次请求2.3用户执行第二次请求2.4 3,4,5,6....次请求2.5 关闭服务器3.servlet方法解析4.适配器模式改造servlet4.1不使用servlet模式4.2使用适配…...
谁是世界上最好的编程语言?--编程语言70年浅谈
1、编程语言发展史纵览 严谨起见,本文提到的编程语言指的是「第三代高级编程语言」。 首先,我们从时间维度入手聊聊编程语言。一图胜千言,我们从目前主流的编程语言中,挑选出流行的、具有历史影响力的语言。把它们按时间从上往下…...
Webpack前端资源加载/打包工具
文章目录一、Webpack1、什么是Webpack2、Webpack安装2.1全局安装2.2安装后查看版本号3、创建项目3.1初始化项目3.2创建src文件夹3.3 src下创建common.js3.4 src下创建utils.js3.5 src下创建main.js4、JS打包4.1创建配置文件4.2执行编译命令4.3创建入口页面4.4测试5、CSS打包5.1…...
springcloud3 fegin实现服务调用1
一 Fegin的作用 1.1 fegin的作用 fegin是一个声明式的web服务客户端,让编写web服务器客户端变得非常容易,只需创建一个接口并在接口中添加FeginClients注解即可。 Fegin的使用方式:使用fegin的注解定义接口,调用这个接口&#…...
商品网站做推广方案/嘉兴新站seo外包
今天看程序,突然发现了 ^ 这个符号,因为我原来很少用 ^ (位逻辑异或) ,一开始没反应过来,baidu google基本无法检索特殊符号的,但知道它是个复合赋值运算符,所以查到了 ^ 位异或赋值运算符。 下面就整理一下…...
营销型网站建设大千建站/深圳网络推广怎么做
我们继续进行设计,根据上节,我们已经设计了小鸟类和管道类。剩下的就是得分和碰撞监测。下面就逐一进行设计。 根据游戏设想,当小鸟飞过管道,玩家得分加1.这里对于飞过管道的逻辑做了简化处理:当管道移动到窗体左侧一…...
百度推广seo软件/百度小程序优化
废话不多说,请看以下代码: #define buffer ((char *) *( (int far *)0x200 )) main(){buffer(char *)malloc(20);buffer[10]0;while(buffer[10]!8){buffer[buffer[10]]a buffer[10];buffer[10];}free(buffer); }我们不妨忽略#define buffer ...这个语句…...
找人做效果土去那网站找/网络广告网站
四川在线记者 雷倢8月20日,成都市2020年8月现代服务业重大项目集中开工仪式在现代商贸生态圈双流航空经济区举行。8月成都实现新开工服务业项目55个(产业功能区内项目41个),总投资434.9亿元,2020年计划投资87.6亿元,其中服务业产业…...
做投票链接的网站/网络服务网络推广
AQS 系列: 【JUC源码】JUC核心:AQS(一)底层结构分析【JUC源码】JUC核心:AQS(二)同步队列源码分析(独占锁)【JUC源码】JUC核心:AQS(三)…...
Vps wordpress https/百度网站推广费用多少
一、什么是JAVA的消息服务 上文中我们提到Java消息服务指的是两个应用程序之间进行异步通信的API,它为标准消息协议和消息服务提供了一组通用接口,包括创建、发送、读取消息等,用于支持JAVA应用程序开发。在J2EE中,当两个应用程序…...