ThreadLocal(超详细介绍!!)
关于ThreadLocal,可能很多同学在学习Java的并发编程部分时,都有所耳闻,但是如果要仔细问ThreadLocal是个啥,我们可能也说不清楚,所以这篇博客旨在帮助大家了解ThreadLocal到底是个啥?
1.ThreadLocal是什么?
首先,我们要知道的是,ThreadLocal类位于Java标准库的java.lang包中,它是Java中的一个类,我们可以用它来声明一个ThreadLocal变量,如下:
ThreadLocal<String> local = new ThreadLocal<>();
好的,接下来解释一下ThreadLocal的含义:
它从名字上看,叫做本地线程变量,意思是说,ThreadLocal中填充的的是当前线程的变量,该变量对其他线程而言是封闭且隔离的,ThreadLocal为变量在每个线程中创建了一个副本,这样每个线程都可以访问自己内部的副本变量。
相信上面的文字描述大家会不太理解,简单来说,就是用ThreadLocal创建的变量,我们可能会在不同的线程中用到,那么为了避免线程安全问题,每个线程都会为自己单独存一份这个变量,并且单独使用和修改这个变量,这样不同的线程之间就各自使用各自的ThreadLocal变量,互不影响。
但是到底具体每个线程是怎样存储的这个变量,以及这个变量如何被这个线程调用,这些的底层是如何实现的,请接着向下看。
2.举例
大家先看一个例子:
public class ThreadLocalTest02 {public static void main(String[] args) {
//创建一个ThreadLocal变量,名为localThreadLocal<String> local = new ThreadLocal<>();
//创建10个线程,并且每次都在不同的线程中加入这个local变量IntStream.range(0, 10).forEach(i -> new Thread(() -> {
//使用set方法设置加入的local内容local.set(Thread.currentThread().getName() + ":" + i);
//然后输入当前线程存储的local变量的信息System.out.println("线程:" + Thread.currentThread().getName() + ",local:" + local.get());}).start());}
}
结果如下:
输出结果:
线程:Thread-0,local:Thread-0:0
线程:Thread-1,local:Thread-1:1
线程:Thread-2,local:Thread-2:2
线程:Thread-3,local:Thread-3:3
线程:Thread-4,local:Thread-4:4
线程:Thread-5,local:Thread-5:5
线程:Thread-6,local:Thread-6:6
线程:Thread-7,local:Thread-7:7
线程:Thread-8,local:Thread-8:8
线程:Thread-9,local:Thread-9:9
上面的结果说明了什么呢?我们在每个线程中都添加了local对象,并且内容是不同的,然后我们再使用get方法输出local的值。我们发现,我们只使用了一个local对象,但是在十个线程中的值都是不同的,而且它们的值不会相互影响,这就是ThreadLocal的简单应用。不同的线程对这个local对象有着自己的备份。
3.Set方法
请大家先仔细阅读一下下面这段源码,逻辑一点也不难,我加了注释:
public class ThreadLocal<T> {public void set(T value) {
//先获取当前线程,例如在线程1中调用了local.set方法,那么这个t就是线程1Thread t = Thread.currentThread();//然后获取当前线程1中的ThreadLocalMapThreadLocalMap map = getMap(t);//如果map为空,说明此线程还没有存入任何一个ThreadLocal对象,我们就创建一个ThreadLocalMap
//如果map不为空,那么我们就直接将value存入这个ThreadLocalMap中if (map != null)map.set(this, value);elsecreateMap(t, value);}
大家现在可能会有疑惑,什么是ThreadLocalMap啊?为啥是从当前线程中获取啊?还有createMap方法,到底是干啥的捏?我们一一进行解释:
什么是ThreadLocalMap?
我们刚才上面说,我们每个线程都会存储ThreadLocal对象的备份,那么存储在哪里呢,答案就是在ThreadLocalMap中,ThreadLocalMap为 ThreadLocal的一个静态内部类,里面定义了Entry来保存数据,那么既然是map,就会有键值对的结构,键的位置存的就是我们的ThreadLocal对象,而值存储的就是通过set方法存入的那个值,例如这一句代码:local.set( i);那么存到这个线程中的ThreadLocalMap的一个entry中,键和值就分别是 local:i
接下来我们就看一下ThreadLocalMap(它是ThreadLocal的一个内部类),还有Entry的结构:
// 内部类ThreadLocalMapstatic class ThreadLocalMap {static class Entry extends WeakReference<ThreadLocal<?>> {Object value;// 内部类Entry,实际存储数据的地方// Entry的key是ThreadLocal对象,不是当前线程ID或者名称Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}// 注意这里维护的是Entry数组private Entry[] table;}
可以看出实际上存储数据的是Entry,而TheadLocalMap则是一个Entry数组;
ok,了解了这个结构后,我们又回到宏观的角度看待一下问题,刚才说每个线程都有自己的备份,并且这些备份是当前线程独有的,那么既然上面说存储数据的是ThreadLocalMap,并且每个线程都有自己的独有的一份,那么这个ThreadLocalMap到底存在哪里呢?答案就是:ThreadLocalMap是作为Thread类的一个私有属性实现的,这样就可以保证每个Thread线程都有自己独一份的TheadLocalMap来存储自己的Threadlocal变量。
public class Thread {/* ... 省略其他代码 ... *//*** ThreadLocalMap实例,用于存储ThreadLocal变量的键值对*/ThreadLocal.ThreadLocalMap threadLocals = null;/* ... 省略其他代码 ... */
}
好好好,现在我们算是知道了,原来为每个线程存储这些ThreadLocal变量的,就是Thread类中的属性threadLocals
那么这样我们就能解释为什么要从线程中获取map了,看一下刚才的set方法中的这一句,我们就知道为啥要从线程中获取了:
//然后获取当前线程1中的ThreadLocalMapThreadLocalMap map = getMap(t);
接着就是后面的代码,相信大家也就能明白为什么要这样写啦:
//如果map为空,说明此线程还没有存入任何一个ThreadLocal对象,我们就创建一个ThreadLocalMap
//如果map不为空,那么我们就直接将value存入这个ThreadLocalMap中if (map != null)map.set(this, value);elsecreateMap(t, value);}
这是createMap方法,看到它给什么赋值吗,就是我们刚才说的Thread线程类中的那个存储ThreadLocalMap的属性哦~
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}
ok,set方法就介绍到这里了
4.get方法
get方法的作用很简单,它通过local对象调用,返回当前线程的以local对象为键,对应的那个值即可;例如 System.out.println(local.get());就是输出当前线程的local对象当时通过set方法存入的值。
get方法的源码如下:
public T get() {
//先获取当前调用get方法的线程Thread t = Thread.currentThread();//然后获取此线程的ThreadLocalMap对象,这里面存储着local键值对ThreadLocalMap map = getMap(t);/*如果map不为空,就在map里面寻找键为this的entry,为什么是this呢,因为当前类
是ThreadLocal类,而get方法通过local.get()的方式调用,所以这里的this就指的
是这个local对象,也就是entry的键。如果找的了这个以local为键的entry,我们就
返回对应的值即可。
*/if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}
ok,ThreadLocal的具体应用和get方法就介绍到这里
5.ThreadLocal的结构
有了上面的基础,我们现在来看一下他在内存中的结构:

6.内存泄漏问题
仔细看下ThreadLocal内存结构就会发现,Entry数组对象通过ThreadLocalMap最终被Thread持有,并且是强引用。也就是说Entry数组对象的生命周期和当前线程一样。即使ThreadLocal对象被回收了,Entry数组对象也不一定被回收,这样就有可能发生内存泄漏。ThreadLocal在设计的时候就提供了一些补救措施:
- Entry的key是弱引用的ThreadLocal对象,很容易被回收,导致key为null(但是value不为null)。所以在调用get()、set(T)、remove()等方法的时候,会自动清理key为null的Entity。
- remove()方法就是用来清理无用对象,防止内存泄漏的。所以每次用完ThreadLocal后需要手动remove()。
解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。
如同 lock 的操作最后要执行解锁操作一样,ThreadLocal使用完毕一定记得执行remove 方法,清除当前线程的数值。如果不remove 当前线程对应的VALUE ,就会一直存在这个值。
这里复习一下对象的强引用、软引用、弱引用
1.强引用
我们平日里面的用到的new了一个对象就是强引用,例如
Object obj = new Object();当JVM的内存空间不足时,宁愿抛出OutOfMemoryError使得程序异常终止也不愿意回收具有强引用的存活着的对象!2.软引用
当JVM认为内存空间不足时,就回去试图回收软引用指向的对象,也就是说在JVM抛出
OutOfMemoryError之前,会去清理软引用对象。3.弱引用
在GC的时候,不管内存空间足不足都会回收这个对象,同样也可以配合
ReferenceQueue使用,也同样适用于内存敏感的缓存。ThreadLocal中的key就用到了弱引用。
7.最后我们还要知道为什么要使用ThreadLocal?
ThreadLocal类在多线程编程中有几个重要的用途和优势:
-
线程隔离:ThreadLocal提供了一种将数据与线程关联的机制。通过使用ThreadLocal,可以为每个线程创建独立的变量副本,使得每个线程都可以独立地访问和修改自己的变量副本,而不会干扰其他线程的数据。这样可以实现线程间的数据隔离,避免了线程安全问题。
-
状态传递:ThreadLocal可以用于在同一个线程的多个方法之间传递状态信息,而无需在方法参数中显式传递。通过将状态信息存储在ThreadLocal变量中,不同的方法可以通过ThreadLocal访问和修改共享的状态,而无需显式传递参数。这样可以简化方法的调用,提高代码的可读性和可维护性。
-
线程上下文管理:有些情况下,需要在整个线程执行期间共享某些上下文信息,比如用户认证信息、数据库连接等。通过将这些信息存储在ThreadLocal中,可以在同一线程的任何地方方便地访问和使用这些信息,而无需显式传递或在每个方法中重复获取。
-
避免锁竞争:在某些情况下,使用ThreadLocal可以避免使用锁来同步对共享变量的访问。由于每个线程都有自己的变量副本,线程之间不会产生竞争条件,从而避免了锁竞争和同步开销,提高了程序的性能。
相关文章:
ThreadLocal(超详细介绍!!)
关于ThreadLocal,可能很多同学在学习Java的并发编程部分时,都有所耳闻,但是如果要仔细问ThreadLocal是个啥,我们可能也说不清楚,所以这篇博客旨在帮助大家了解ThreadLocal到底是个啥? 1.ThreadLocal是什么&…...
stable diffusion安装包和超火使用文档,数字人制作网址
一:文生图、图生图 1:stable diffusion:对喜欢二次元、美女小姐姐、大眼萌妹的人及其友好哈哈(o^^o) 1):秋叶大神安装包和模型包: 链接:https://pan.baidu.com/s/11_kguofh76gwhTBPUipepw 提…...
JVM——HotSpot的算法细节实现
一、根节点枚举 固定可作为GC Roots的节点主要在全局性的引用(如常量或类静态属性)与执行上下文(如栈帧中的本地变量表)中,尽管目标明确,但查找要做到高效很难。现在java应用越来越庞大,光方法区…...
高等数学教材重难点题型总结(三)微分中值定理和导数的应用
第三章,微分中值定理的证明题等,非常重要,需要牢牢掌握 1.证明中值定理对某函数在给定区间上的正确性 2.与中值定理有关的证明题 3.微分中值定理应用于求证不等式 4.洛必达法则求极限 5.洛必达的经典错误反例 6.按某项实现多项式幂展开 7.求带…...
神经网络基础-神经网络补充概念-39-梯度消失与梯度爆炸
简介 梯度消失和梯度爆炸是在深度神经网络中训练过程中可能出现的问题,导致模型难以训练或无法收敛。这些问题与反向传播算法中的梯度计算有关。 概念 梯度消失(Gradient Vanishing):在深层神经网络中,特别是具有很…...
P12-Retentive NetWork-RetNet挑战Transformer
论文地址:https://arxiv.org/abs/2307.08621 目录 Abstract 一.Introduction 二.Retentive Networks 2.1Retention 2.2Gated Multi-Scale Retention 2.3Overall Architecture of Retention Networks 2.4Relation to and Differences from Previous Methods 三.Experime…...
数据库基础
什么是数据库? 数据库本质上也是一个文件,它是按照特定结构组织在一起的数据的集合!是存储数据、管理数据、组织数据的一套解决方案! 可是存储数据用文件就够了,为什么还要弄个数据库? 主要是因为ÿ…...
【RocketMQ】安装
文章目录 下载RocketMQ配置环境变量 下载RocketMQ 下载RocketMQ安装包 下载DashBoard 这里版本推荐选择4.9.x,因为比较稳定。 下载完毕之后,将安装包拖入到Linux环境。 之后使用unzip命令解压缩RocketMQ的安装包。 unzip ./rocketmq-all-4.9.2-bin-rel…...
二十二、策略模式
目录 1、项目需求2、传统方案解决鸭子问题的分析和代码实现3、传统方式实现存在的问题分析和解决方案4、策略模式基本介绍5、使用策略模式解决鸭子问题6、策略模式的注意事项和细节7、策略模式的使用场景 以具体项目来演示为什么需要策略模式,策略模式的优点&#x…...
[K8s]问题描述:k8s拉起来的容器少了cuda的so文件
问题解决:需要设置Runtimes:nvidia的同时设置Default Runtimenvidia...
Postman如何做接口测试:什么?postman 还可以做压力测试?
我们都知道, postman 是一款很好用的接口测试工具。不过 postman 还可以做简单的压力测试,而且步骤只需要 2 步。 首先,打开 postman, 编写接口的请求参数。 然后,点击右下方的 runner 运行器,把需要测试的接口拖动到…...
MySQL 自增 ID 默认从 1 开始,如何设置自增 ID 从 0 开始
MySQL 是一种关系型数据库,它是世界上最流行的关系型数据库之一。在 MySQL 中,自增是一种非常有用的功能,它可以自动给主键赋值,并保证每个主键是唯一的。然而,许多人不知道的是,MySQL 默认情况下从 1 开始…...
WebRTC音视频通话-实现iOS端调用ossrs视频通话服务
WebRTC音视频通话-实现iOS端调用ossrs视频通话服务 之前搭建ossrs服务,可以查看:https://blog.csdn.net/gloryFlow/article/details/132257196 这里iOS端使用GoogleWebRTC联调ossrs实现视频通话功能。 一、iOS端调用ossrs视频通话效果图 iOS端端效果图…...
uniapp的UI框架组件库——uView
在写uniapp项目时候,官方所推荐的样式库并不能满足日常的需求,也不可能自己去写相应的样式,费时又费力,所以我们一般会去使用第三方的组件库UI,就像vue里我们所熟悉的elementUI组件库一样的道理,在uniapp中…...
由于找不到msvcp140.dll无法继续执行代码是什么原因
使用计算机过程中,有时会遇到一些错误提示,其中之一就是关于msvcp140.dll文件丢失或损坏的错误。msvcp140.dll是Windows系统中非常重要的文件,是Microsoft Visual C Redistributable中动态链接库的文件,如果缺失或损坏,…...
kafka生产者幂等与事务
目录 前言: 幂等 事务 总结: 参考资料 前言: Kafka 消息交付可靠性保障以及精确处理一次语义的实现。 所谓的消息交付可靠性保障,是指 Kafka 对 Producer 和 Consumer 要处理的消息提供什么样的承诺。常见的承诺有以下三…...
Docker容器:docker基础概述、安装、网络及资源控制
文章目录 一.docker容器概述1.什么是容器2. docker与虚拟机的区别2.1 docker虚拟化产品有哪些及其对比2.2 Docker与虚拟机的区别 3.Docker容器的使用场景4.Docker容器的优点5.Docker 的底层运行原理6.namespace的六项隔离7.Docker核心概念 二.Docker安装 及管理1.安装 Docker1.…...
实验篇——亚细胞定位
实验篇——亚细胞定位 文章目录 前言一、亚细胞定位的在线网站1. UniProt2. WoLFPSORT3. BUSCA4. TargetP-2.0 二、代码实现1. 基于UniProt(不会)2. 基于WoLFPSORT后续(已完善,有关代码放置于[python爬虫学习(一&#…...
【日常积累】HTTP和HTTPS的区别
背景 在运维面试中,经常会遇到面试官提问http和https的区别,今天咱们先来简单了解一下。 超文本传输协议HTTP被用于在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果…...
Qt creator之对齐参考线——新增可视化缩进功能
Qt creator随着官方越来越重视,更新频率也在不断加快,今天无意中发现qt creator新版有了对齐参考线,也称可视化缩进Visualize Indent,默认为启用状态。 下图为旧版Qt Creator显示设置栏: 下图为新版本Qt Creator显示设…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
Flask RESTful 示例
目录 1. 环境准备2. 安装依赖3. 修改main.py4. 运行应用5. API使用示例获取所有任务获取单个任务创建新任务更新任务删除任务 中文乱码问题: 下面创建一个简单的Flask RESTful API示例。首先,我们需要创建环境,安装必要的依赖,然后…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
AI编程--插件对比分析:CodeRider、GitHub Copilot及其他
AI编程插件对比分析:CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展,AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者,分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...
【C++从零实现Json-Rpc框架】第六弹 —— 服务端模块划分
一、项目背景回顾 前五弹完成了Json-Rpc协议解析、请求处理、客户端调用等基础模块搭建。 本弹重点聚焦于服务端的模块划分与架构设计,提升代码结构的可维护性与扩展性。 二、服务端模块设计目标 高内聚低耦合:各模块职责清晰,便于独立开发…...
CMake控制VS2022项目文件分组
我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...
嵌入式学习笔记DAY33(网络编程——TCP)
一、网络架构 C/S (client/server 客户端/服务器):由客户端和服务器端两个部分组成。客户端通常是用户使用的应用程序,负责提供用户界面和交互逻辑 ,接收用户输入,向服务器发送请求,并展示服务…...
莫兰迪高级灰总结计划简约商务通用PPT模版
莫兰迪高级灰总结计划简约商务通用PPT模版,莫兰迪调色板清新简约工作汇报PPT模版,莫兰迪时尚风极简设计PPT模版,大学生毕业论文答辩PPT模版,莫兰迪配色总结计划简约商务通用PPT模版,莫兰迪商务汇报PPT模版,…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
【51单片机】4. 模块化编程与LCD1602Debug
1. 什么是模块化编程 传统编程会将所有函数放在main.c中,如果使用的模块多,一个文件内会有很多代码,不利于组织和管理 模块化编程则是将各个模块的代码放在不同的.c文件里,在.h文件里提供外部可调用函数声明,其他.c文…...
