线程私有变量ThreadLocal详解
本文已收录至Github,推荐阅读 👉 Java随想录
烈火试真金,逆境试强者。——塞内加
文章目录
- 什么是ThreadLocal
- ThreadLocal 原理
- set()方法
- get()方法
- remove()方法
- ThreadLocal 的Hash算法
- ThreadLocal 1.7和1.8的区别
- ThreadLocal 的问题
- ThreadLocal 内存泄露问题
- 为什么使用弱引用而不是强引用?
- ThreadLocal 父子线程继承
什么是ThreadLocal
首先看下ThreadLocal的使用示例:
public class ThreadLocalTest {private static ThreadLocal<String> threadLocal = new ThreadLocal<>();public static void main(String[] args) {Thread thread1 = new Thread(() -> {threadLocal.set("本地变量1");print("thread1");System.out.println("线程1的本地变量的值为:"+threadLocal.get());});Thread thread2 = new Thread(() -> {threadLocal.set("本地变量2");print("thread2");System.out.println("线程2的本地变量的值为:"+threadLocal.get());});thread1.start();thread2.start();}public static void print(String s){System.out.println(s+":"+threadLocal.get());}
执行结果如下
我们从 Thread
类讲起,在 Thread
类中有维护两个 ThreadLocal.ThreadLocalMap
对象,分别是:threadLocals
和inheritableThreadLocals
。
/* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;/** InheritableThreadLocal values pertaining to this thread. This map is* maintained by the InheritableThreadLocal class.*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
初始它们都为 null,只有在调用 ThreadLocal
类的 set 或 get 时才创建它们。ThreadLocalMap可以理解为线程私有的HashMap。
ThreadLoalMap是ThreadLocal中的一个静态内部类,类似HashMap的数据结构,但并没有实现Map接口。
ThreadLoalMap中初始化了一个大小16的Entry数组,Entry对象用来保存每一个key-value键值对。key是ThreadLocal对象。
Entry用来保存数据 ,而且还是继承的弱引用。在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value。
ThreadLocal 原理
set()方法
当我们调用 ThreadLocal 的 set()
方法时实际是调用了当前线程的 ThreadLocalMap 的 set() 方法。ThreadLocal 的 set() 方法中,会进一步调用Thread.currentThread()
获得当前线程对象 ,然后获取到当前线程对象的ThreadLocal,判断是不是为空,为空就先调用creadMap()
创建再set(value)
创建 ThreadLocalMap 对象并添加变量。不为空就直接set(value)
。
这种保证线程安全的方式称为线程封闭
。线程只能看到自己的ThreadLocal变量。线程之间是互相隔离的。
get()方法
其中get()方法用来获取与当前线程关联的ThreadLocal的值,如果当前线程没有该ThreadLocal的值,则调用initialValue函数获取初始值返回,所以一般我们使用时需要继承该函数,给出初始值(不重写的话默认返回Null)。
主要有以下几步:
- 获取当前的Thread对象,通过getMap获取Thread内的ThreadLocalMap
- 如果map已经存在,以当前的ThreadLocal为键,获取Entry对象,并从从Entry中取出值
- 否则,调用setInitialValue进行初始化。
/*** Returns the value in the current thread's copy of this* thread-local variable. If the variable has no value for the* current thread, it is first initialized to the value returned* by an invocation of the {@link #initialValue} method.** @return the current thread's value of this thread-local*/
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();
}
我们可以重写initialValue()
,设置初始值。
private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){@Overrideprotected Integer initialValue() {return Integer.valueOf(0);}}
remove()方法
最后一个需要探究的就是remove方法,它用于在map中移除一个不用的Entry。也是先计算出hash值,若是第一次没有命中,就循环直到null,在此过程中也会调用expungeStaleEntry清除空key节点。代码如下:
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;}}
}
实际上 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉。
所以如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候会被清理掉的,这样一来 ThreadLocalMap中使用这个 ThreadLocal 的 key 也会被清理掉。但是,value 是强引用,不会被清理,这样一来就会出现 key 为 null 的 value。出现内存泄漏的问题。
在执行 ThreadLocal 的 set、remove、rehash 等方法时,它都会扫描 key 为 null 的 Entry,如果发现某个 Entry 的 key 为 null,则代表它所对应的 value 也没有作用了,所以它就会把对应的 value 置为 null,这样,value 对象就可以被正常回收了。但是假设 ThreadLocal 已经不被使用了,那么实际上 set、remove、rehash 方法也不会被调用,与此同时,如果这个线程又一直存活、不终止的话,那么刚才的那个调用链就一直存在,也就导致了 value 的内存泄漏。
ThreadLocal 的Hash算法
ThreadLocalMap
类似HashMap,它有自己的Hash算法。
private final int threadLocalHashCode = nextHashCode();private static final int HASH_INCREMENT = 0x61c88647;private static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT);
}public final int getAndAdd(int delta) {return unsafe.getAndAddInt(this, valueOffset, delta);
}
HASH_INCREMENT
这个数字被称为斐波那契数 也叫 黄金分割数,带来的好处就是 hash
分布非常均匀。
每当创建一个ThreadLocal
对象,这个ThreadLocal.nextHashCode
这个值就会增长 0x61c88647
。
讲到Hash就会涉及到Hash冲突,跟HashMap通过链地址法不同的是,ThreadLocal是通过线性探测法/开放地址法来解决hash冲突。
ThreadLocal 1.7和1.8的区别
ThreadLocal 1.7版本的时候,entry对象的key是Thread。
1.8版本entry的key是ThreadLocal。
1.8版本的好处 :当Thread销毁的时候,ThreadLocalMap也会随之销毁,减少内存的使用。因为ThreadLocalMap是在Thread里面的,所以只要Thread消失了,那ThreadLocalMap就不复存在了。
ThreadLocal 的问题
ThreadLocal 内存泄露问题
在 ThreadLocalMap 中的 Entry 的 key 是对 ThreadLocal 的 WeakReference
弱引用,而 value 是强引用。当 ThreadLocalMap 的某 ThreadLocal 对象只被弱引用,GC 发生时该对象会被清理,此时 key 为 null,但 value 为强引用不会被清理。此时 value 将访问不到也不被清理掉就可能会导致内存泄漏。
注意构造函数里的第一行代码super(k),这意味着ThreadLocal对象是一个弱引用
/*** The entries in this hash map extend WeakReference, using* its main ref field as the key (which is always a* ThreadLocal object). Note that null keys (i.e. entry.get()* == null) mean that the key is no longer referenced, so the* entry can be expunged from table. Such entries are referred to* as "stale entries" in the code that follows.*/
static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}
}
因此我们使用完 ThreadLocal 后最好手动调用 remove()
方法。但其实在 ThreadLocalMap 的实现中以及考虑到这种情况,因此在调用 set()
、get()
、remove()
方法时,会清理 key 为 null 的记录。
为什么使用弱引用而不是强引用?
为什么采用了弱引用的实现而不是强引用呢?
注释上有这么一段话:为了协助处理数据比较大并且生命周期比较长的场景,hash table的条目使用了WeakReference作为key。
所以,弱引用反而是为了解决内存存储问题而专门使用的。
实际上,采用弱引用反而多了一层保障,ThreadLocal被清理后key为null,对应的value在下一次ThreadLocalMap调用set、get,就算忘记调用 remove 方法,弱引用比强引用可以多一层保障。
所以,内存泄露的根本原因是是否手动清除操作,而不是弱引用。
ThreadLocal 父子线程继承
异步场景下无法给子线程共享父线程的线程副本数据,可以通过 InheritableThreadLocal
类解决这个问题。
它的原理就是子线程是通过在父线程中调用 new Thread()
创建的,在 Thread 的构造方法中调用了 Thread的init
方法,在 init
方法中父线程数据会复制到子线程(ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
)。
代码示例:
public class InheritableThreadLocalDemo {public static void main(String[] args) {ThreadLocal<String> threadLocal = new ThreadLocal<>();ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();threadLocal.set("父类数据:threadLocal");inheritableThreadLocal.set("父类数据:inheritableThreadLocal");new Thread(new Runnable() {@Overridepublic void run() {System.out.println("子线程获取父类threadLocal数据:" + threadLocal.get());System.out.println("子线程获取父类inheritableThreadLocal数据:" +inheritableThreadLocal.get());}}).start();}
}
但是我们做异步处理都是使用线程池,线程池会复用线程会导致问题出现。我们可以使用阿里巴巴的TTL解决这个问题。
https://github.com/alibaba/transmittable-thread-local
如果本篇博客有任何错误和建议,欢迎给我留言指正。文章持续更新,可以关注公众号第一时间阅读。
相关文章:
线程私有变量ThreadLocal详解
本文已收录至Github,推荐阅读 👉 Java随想录 烈火试真金,逆境试强者。——塞内加 文章目录什么是ThreadLocalThreadLocal 原理set()方法get()方法remove()方法ThreadLocal 的Hash算法ThreadLocal 1.7和1.8的区别ThreadLocal 的问题ThreadLoca…...
如何保证数据库和缓存双写一致性
前言 数据库和缓存(比如:redis)双写数据一致性问题,是一个跟开发语言无关的公共问题。尤其在高并发的场景下,这个问题变得更加严重。 我很负责的告诉大家,该问题无论在面试,还是工作中遇到的概率非常大,所以非常有必要跟大家一起探讨一下。 今天这篇文章我会从浅入深,…...
一文搞懂:JS严格模式“use strict”
什么是JS的严格模式 JS严格模式概念是在ES5中引入的,在此模式下,JS对语法的要求会变的严格,某些不太严谨的代码在严格模式下将不能运行。 如何启用严格模式 只需要JS代码的起始添加 "use strict"即可。如: "use…...
Linux的ACL(扩展权限)规划:setfacl、getfacl
目录 什么是ACL与如何支持启动ACL ACL设置技巧:getfacl、setfacl getfacl命令用法 setfacl命令用法 最简单的【u:账号:权限】设置 使用默认权限设置目录未来文件的ACL权限继承 什么是ACL与如何支持启动ACL ACL是Access Control List的…...
HTML预格式化文本pre标签
文章目录参考white-spaceword-breakfont-family参考 https://blog.csdn.net/weixin_44368963/article/details/120054949 https://www.zhangxinxu.com/wordpress/2017/03/css-font-family-chinese-english/ pre 元素可定义预格式化的文本。被包围在 pre 元素中的文本通常会保留…...
基于机器学习的心脏病预测方法(11)——梯度提升机(GBM)
一、梯度提升机介绍 GBM(Gradient Boosting Machine)算法是Boosting(提升)算法的一种。主要思想是,串行地生成多个弱学习器,每个弱学习器的目标是拟合先前累加模型的损失函数的负梯度, 使加上该弱学习器后的累积模型损失往负梯度的方向减少。 且它用不同的权重将基学习器…...
Linux多版本python切换以及多版本pip对应 (cloud studio Ubuntu16.04)
linux && cloud studio && Ubuntu16.04 简单解决多版本python切换以及多版本pip对应问题 1.python2切换成python 多版本python: 更改前先查看版本号 $ python -V Python 2.7.12 $ python2 -V Python 2.7.12 $ python3 -V Python 3.5.2 通过下面的命令看到py…...
【并发编程】LockSupport源码详解
目录 一、前言 1.1 简介 1.2 为什么说LockSupport是Java并发的基石? 二、LockSupport的用途 2.1 LockSupport的主要方法 2.2 使用案例 2.3 总结 三、LockSupport 源码分析 3.1 学习原理前的前置知识 3.1.1 Unsafe.park()和Unsafe.unpark() 3.1.2wait和notify/notify…...
元宇宙之声:新鸿基公司
在本期节目中,新鸿基团队讲述了他们在农历新年季中展示的元宇宙最新创作! 为什么将体验命名为「乘风启航」?什么是 「Scallywag」? 香港专业离岸帆船队新鸿基 Scallywag 队由新鸿基公司赞助,其团队精神与公司的精神相呼…...
Linux中定时监控Tomcat服务器进程并在进程结束时重启Tomcat服务器
目录一、问题二、解决方法1、创建定时任务文件2、修改Tomcat的部分文件3、添加系统的定时调度4、执行monitor.sh文件5、查看脚本执行的日志文件一、问题 当我们的Tomcat配置完成后投入使用后,在用户使用一定时间后,Tomcat可能会出现一些问题导致进程结束…...
快速部署私有云笔记,免费享受多端同步
一、老Q笔记之一路坎坷 市面上的笔记软件非常多,有些是本地编辑功能特别强大但是不支持云同步,有些是支持上云但是编辑功能不够完善。选择一款合适的云笔记软件,无疑能让我们工、学习的时候更加顺心、顺手。 这么多年来老Q使用过很多云笔记…...
python生成 2048位随机质数 Miller-Rabin质数测试算法
Miller-Rabin质数测试算法是一种基于随机化的算法,用于判断一个数是否为质数。该算法具有高效性和强健性,通常被用于加密算法中生成大素数。 该算法基于以下两个事实:对于质数ppp和任意整数aaa,有ap−1≡1(modp)a^{p-1} \equiv 1…...
♡ — MySQL 查询缓存
MySQL 查询缓存 执行查询语句的时候,会先查询缓存。MySQL 8.0 版本后移除,因为这个功能不太实用。 my.cnf 加入以下配置,重启 MySQL 开启查询缓存: query_cache_type1 query_cache_size600000MySQL 执行以下命令也可以开启查询缓…...
死锁检测组件 -- 使用hook检测死锁
目录 hook hook是什么 dlsym()函数 hook的实现步骤 加入hook的demo C/CLinux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂 hook hook可以把系统或第三方库提供的函数,替换成我们写的同名函数。会调用我们实现的函数。 hook是什么 hook提供了两…...
第2集丨Java中的数据类型汇总
目录一、数据类型分类二、基本数据类型取值范围数据类型的转换byte和char的关系三、包装类一、数据类型分类 二、基本数据类型 取值范围 比特(bit位) : 数据运算得最小存储单位字节(byte) : 数据最小存储单位bit和byte可以互相转换得,1 byte 8 bit位默认情况下&am…...
【基础篇】7 # 队列:队列在线程池等有限资源池中的应用
说明 【数据结构与算法之美】专栏学习笔记 什么是队列? 队列是一种操作受限的线性表数据结构,特点是先进先出,最基本的操作有:入队 enqueue(),放一个数据到队列尾部;出队 dequeue(),从队列头…...
matlab进行双目标定获取双目参数并打印教程
文章目录前言1.打开matlab进行双目标定2.获取想要的参数前言 在相同的标定算法和标定参数下,Python和Matlab的标定精度是相同的。因为标定精度主要取决于标定算法和标定参数的质量,而不是编程语言的选择。 不同的编程语言可能使用不同的库或实现细节&…...
JVM类加载机制
回到2018年的抖音哈哈. 回顾下: java开发环境: java编译运行过程: 1) 编译期:.java源文件,经过编译,生成.class字节码文件 2) 运行期:JVM加载.class并运行.class(0和1) 特点: 跨平台、一次编程,处处报错 名词解释: 1…...
8.1 优化概述
数据库性能取决于数据库级别的几个因素,例如表、查询和配置设置。这些软件结构导致了硬件级别的 CPU 和 I/O 操作,您必须将其最小化并尽可能提高效率。在研究数据库性能时,首先要学习软件端的高级规则和准则,然后使用墙上的时钟时…...
从0到1一步一步玩转openEuler--14 openEuler DNF(YUM)配置管理
文章目录14.1 DNF配置文件14.1.1 配置main部分14.1.2 配置repository部分14.1.3 显示当前配置14.2 创建本地软件源仓库14.3 添加、启用和禁用软件源14.3.1 添加软件源14.3.2 禁用软件源14.3.3 启用软件源DNF是一款Linux软件包管理工具,用于管理RPM软件包。DNF可以查…...
leetcode707 设计链表 带有输入和输出的
题目: 设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节…...
100种思维模型之非sr思维模型-012
什么是sr? sr是stimulus-response的缩写,意思是刺激反应。 那么非sr思维模型就是非刺激反应思维模型的意思。 今天我们来聊聊非sr思维模型——一个提醒我们思考,提醒我们任何时刻都有选择权的思维模型。 本文依然从三个方面进行介绍,何谓…...
绿竹生物再冲刺港交所上市:暂未商业化,孔健夫妇为实控人
近日,北京绿竹生物技术股份有限公司(下称“绿竹生物”)在港交所递交招股书,准备在港交所主板上市,中金公司为其独家保荐人。据贝多财经了解,绿竹生物曾于2022年6月28日在港交所递表。 相较于此前招股书&am…...
加拿大MSB金融牌照申请方案
什么是加拿大MSB金融牌照? 根据犯罪所得(洗钱)和恐怖主义融资法案,您的企业必须在加拿大金融交易和报告分析中心 (FINTRAC) 注册成为货币服务企业。自 2020 年 6 月 1 日起,外国货币服务企业也必须在 FINTRAC 注册&…...
javaEE 初阶 — 滑动窗口
文章目录滑动窗口1 滑动窗口下如何处理丢包TCP 工作机制:确认应答机制 超时重传机制 连接管理机制 滑动窗口 确认应答机制、超时重传机制、连接管理机制 都是给 TCP 的可靠性提供支持的。 虽然事变的比较可靠了,但是是有牺牲的,那就是传输…...
大咖说·图书分享|狼书(卷3):Node.js高级技术
Node.js都有哪些需要掌握的高级技术?前端为什么同样需要学习? Node.js未来的发展趋势究竟如何?本期大咖说,Node布道师桑世龙携新作《狼书(卷3):Node.js高级技术》展开分享。 ● 嘉宾介绍 桑世龙:Node布道…...
1.5配置NBMA和P2MP网络类型
1.3.3实验5:配置NBMA和P2MP网络类型 1. 实验需求 控制OSPF DR的选举修改OSPF的网络类型2. 实验拓扑 配置NBMA和P2MP网络类型实验拓扑如图1-13所示。 图1-13 配置NBMA和P2MP网络类型 3. 实验步骤 帧中继的配置如图1-14和图1-15所示...
Java面试题
三次握手,四次挥手中,为什么要挥手四次 第一次握手,客户端发送同步报文到服务端,客户端知道自己有发送数据能力,不知道服务端是否有发送、接受数据能力。 第二次握手,服务端收到同步报文,并回复…...
opencv锁定鼠标定位
大家好,我是csdn的博主:lqj_本人 这是我的个人博客主页: lqj_本人的博客_CSDN博客-微信小程序,前端,python领域博主lqj_本人擅长微信小程序,前端,python,等方面的知识https://blog.csdn.net/lbcyllqj?spm1011.2415.3001.5343哔哩哔哩欢迎关注…...
机器连接和边缘计算
以一种高效、可扩展的方式进行连接和边缘计算的结合,解决了在工业物联网应用中的机器数据集成问题。 一 边缘计算 边缘计算描述了由中央平台管理的数据分散式处理。边缘计算对于工业物联网而言非常重要。在许多应用程序中,由于数据量非常大,…...
wordpress头像尺寸/网络推广主要工作内容
UNDO简介 默认分类 2009-02-06 14:49:39 阅读137 评论0 字号:大中小 订阅 UNDO数据也称为回滚(ROLLBACK)数据,它用于确保数据的一致性。 Oracle9i以前,管理UNDO数据需要建立专门的回滚段,从 Oracle9i之后,为了简化UN…...
成都的网站建设开发公司哪家好/如何注册网站平台
防伪码:老骥伏枥,志在千里。7.1 grep过滤来自一个文件或标准输入匹配模式内容。除了 grep 外,还有 egrep、fgrep。egrep 是 grep 的扩展,相当于 grep -E。fgrep 相当于 grep -f,用的少。Usage: grep [OPTION]... PATTE…...
先做网站还是先域名备案/千锋教育培训怎么样
Tomcat各个版本的下载地址包括源码: http://archive.apache.org/dist/tomcat **************** 选择版本 **************** **************** 选择二级版本 **************** **************** 选择bin目录 **************** **************** 找到系统对应的…...
网站如何在百度上做推广方案/网站排名优化需要多久
点击链接→【数字图像处理】简单实践汇总 文章目录1. 添加噪声:椒、盐、椒盐、高斯2. 调用自己写的函数进行平滑处理3. 算数均值4. 几何均值5. 谐波均值6. 逆谐波均值7. 效果展示1. 添加噪声:椒、盐、椒盐、高斯 椒、盐、椒盐噪声比较简单,随…...
深圳市专注网站建设/搜索推广
智器Ten3从一上市就采用了最新的Android4.0系统,随着时间的推移官方已经更新至了最新的Android4.0.3版本。在上一页笔者已经简要的介绍了一下系统升级的步骤,可以看出智器平板的升级操作还是非常简单的。Ten3采用最新的Android4.0.3系统谷歌最新的Androi…...
高端办公室装修公司/seo网站营销公司哪家好
专题5:Java序列化 1、什么是java序列化,如何实现java序列化? 2、保存(持久化)对象及其状态到内存或者磁盘 3、序列化对象以字节数组保持-静态成员不保存 4、序列化用户远程对象传输 5、Serializable 实现序列化 6、writeObject 和 readO…...