【Java核心知识】ThreadLocal相关知识

news/2025/3/14 18:21:40/

ThreadLocal

什么是ThreadLocal

ThreadLoacal类可以为每个线程保存一份独有的变量,该变量对于每个线程都是独占的。实现原理为每个Thread类中包含一个ThreadHashMapkey为变量的namevalue为变量的

在日常使用中,我们可以通过set(value)方法设置值,然后通过get()方法获取值,示例代码如下:

我们通过线程池执行了五个线程,每个线程都有一份自己独有的id变量,实现了线程间的数据隔离。

@Slf4j
public class ThreadLocalTest {// ThreadLocal 变量,每个线程都有自己的副本private static final ThreadLocal<Integer> id = new ThreadLocal<>();@Testpublic void testThreadLocal() {// 创建线程池ExecutorService executorService = Executors.newCachedThreadPool();try {for (int i = 0; i < 5; i ++) {int finalI = i;executorService.execute(() -> {// 设置ThreadLocal 变量    id.set(finalI);// 获取ThreadLocal变量log.info("The id of thread {} is {}", Thread.currentThread().getName(), id.get());});}} finally {id.remove();executorService.shutdown();}}
}

传统的HashMap是链地址法,发生碰撞放入桶内;ThreadLocalMap是开放地址法,发生冲突放到后一位

实现原理

那如何实现这种线程间的数据隔离呢?一种容易想到的方法是在Map中进行存储,每个ThreadLocal变量内维护一个Map,其中key为每个线程的IDvalue为对应的值。这样当调用get()方法时,会通过hash算法找到对应线程的值。

jdk也确实是这么做的,只不过存放元素Map的实现不是HashMap,而是ThreadLocalMap,解决冲突的做法是开放寻址法,即出现冲突就往下一个临近位置找,直到找到空位置。

早期的实现中,多个线程会共享一个ThreadLocalMap,这样随着线程数的增加,map就需要扩容,会消耗较多的资源;

后来,每个线程都有自己的一个ThreadLocalMap,各自保存自己的局部变量,key为变量名,value为变量值。

因为局部变量很少,所以一般不需要扩容。

内存泄漏

下面这张图是ThreadLocal对应的引用关系,可以看到,在新的设计下,ThreadLocalMap存在于Thread中,是和Thread的生命周期一致的。

在这里插入图片描述

ThreadLocalMapkeyThreadLocal对象,value是对应的值,需要注意的是ThreadHashMapkey会使用弱引用,这主要是为了当ThreadLocalnull后,除了ThreadHashMap持有的弱引用外,没有别的强引用,ThreadLocal对象可以被GC回收

但是这样就导致ThreadHashMap中对应的keynull,其value引用的对象不会被GC回收,从而出现了内存泄漏。

所以在使用完ThreadLocal变量后,尽量使用remove()方法进行清理,这样会把keynull的键值对删除。

再回到上面的示例,上面的示例我们使用final修饰了ThreadLocal,保证其不会被修改,这样做会导致ThreadLocal永远存在强引用,不会被释放,容易发生内存泄露,所以需要我们显示调用remove()方法进行清理。

参考链接

ThreadLocal的内存泄露?什么原因?如何避免?


http://www.ppmy.cn/news/1082160.html

相关文章

Systrace分析App性能学习笔记

学习Gracker Systrace系列文章&#xff0c;总结使用Systrace分析App性能的方法。推荐想通过Systrace学习Framework的同学&#xff0c;去看原文。 文章目录 概述Systrace使用流程Systrace 文件生成图形方式(不推荐)命令行方式 Systrace分析快捷键使用帧状态线程状态查看线程唤醒…

习题练习 C语言(暑期第三弹)

自我小提升&#xff01; 前言一、存储地址二、逗号表达式三、除自身以外数组的乘积四、字节与二进制五、符号计算六、不用加减乘除做加法七、unsigned判断八、移位计算九、sizeof宏十、移位计算十一、移位计算十二、优先级判断十三、单词倒排总结 前言 重要的事说三遍&#xf…

C++算法 —— 分治(2)归并

文章目录 1、排序数组2、数组中的逆序对3、计算右侧小于当前元素的个数4、翻转对 1、排序数组 排序数组 排序数组也可以用归并排序来做。 vector<int> tmp;//写成全局是因为如果在每一次小的排序中都创建一次&#xff0c;更消耗时间和空间&#xff0c;设置成全局的就更高…

【C++】在子类中怎么调用父类的方法

2023年8月31日&#xff0c;周四上午 目录 使用父类作用域运算符::子类没有重写父类方法时子类重写了父类方法时使用this指针调用子类没有重写父类方法时 在C中,子类可以以以下几种方式调用父类的方法: 使用父类作用域运算符:: 这种方法既可以在子类没有重写父类方法时使用&am…

Revit SDK 介绍:AutoStamp 自动水印 AutoUpdate 自动更新 CancelSave

前言 这三个例子都是通过注册事件来完成相应的工作&#xff0c;内容比较简单。 内容 事件参考博客&#xff1a;Revit API&#xff1a;Events 事件总览 AutoStamp 自动水印 使用到的事件&#xff1a; application.ControlledApplication.ViewPrinting application.Controll…

大模型的能力边界在哪里?

随着人工智能领域的不断发展&#xff0c;大型神经网络模型已经成为了研究和应用中的主要工具之一。这些大模型&#xff0c;尤其是像GPT-3这样的巨型语言模型&#xff0c;展示了令人印象深刻的自然语言处理能力&#xff0c;甚至能够生成高质量的文本、回答问题、模仿不同的写作风…

VB房屋租凭管理系统设计与实现

摘要 随着我国市场经济的快速发展和人们生活水平的不断提高,简单的租凭服务已经不能满足人们的需求。如何利用先进的管理手段,提高房屋租凭管理水平,是当今社会所面临的一个重要课题。本文通过用VB6.0编写房屋租赁管理系统,利用软件工程原理,采用面向对象的编程方法,其开…

springboot添加ssl证书文件后报错:DerInputStream.getLength(): lengthTag=111, too big.

1、添加证书文件 springboot添加SSL证书&#xff0c;支持https与http 按照上述连接中的方法添加证书文件后&#xff0c;启动报错&#xff1a;DerInputStream.getLength(): lengthTag111, too big. 2、解决办法 在pom.xml中添加配置 再次启动解决问题 <plugin><arti…