当前位置: 首页 > news >正文

Java并发编程第8讲——ThreadLocal详解

ThreadLocal无论是在项目开发还是面试中都会经常碰到,它的重要性可见一斑,本篇文章就从ThreadLocal的使用、实现原理、核心方法的源码、内存泄漏问题等展开介绍一下。

一、什么是ThreadLocal

ThreadLocal是java.lang下面的一个类,在JDK 1.2版本加入,作者是Josh Bloch(集合大神)和Doug Lea(并发大神)

它提供了一种线程局部变量的方式,线程局部变量是指每个线程都拥有自己独立的变量副本,互不干扰,通过ThreadLocal,可以方便地在多线程环境下共享数据,同时不需要考虑线程安全性,这也是解决并发问题的途径之一。

例如:在web开发中,可以使用ThreadLocal来保存用户的登录信息,以便每个线程都能够独立地获取和修改自己的登录信息,避免了线程之间的干扰。

二、ThreadLocal的使用

ThreadLocal有四个方法,分别为:

  • protected T initialValue():返回此线程局部变量的初始值。

  • pubulic T get(): 返回当前线程局部变量的当前线程副本的值。如果这是线程第一次调用该方法,则创建并初始化此副本。

  • public void set(T value):将此线程局部变量的当前线程的副本设置为指定的值。

  • public void remove():移除此线程局部变量的当前线程的值。

下面使用ThreadLocal来模拟用户登录信息的场景:

ThreadLocal工具类:

public class CurrentUserHolder {public static ThreadLocal<User> threadLocal=new ThreadLocal<>();
​public static void setUser(User user){threadLocal.set(user);}
​public static User getUser(){if (Objects.nonNull(threadLocal.get())) {return threadLocal.get();}throw new RuntimeException("当前用户信息为空!");}
​public static void clearUser(){threadLocal.remove();}
}

User实体类:

@Data
public class User {private String name;private Integer age;
}

测试:

public class Test {public static void main(String[] args) {//用户登录User user = new User();user.setName("小黑子");user.setAge(18);//将用户信息保存在ThreadLocal中CurrentUserHolder.setUser(user);//在其它方法中,可以通过ThreadLocal获取用户信息User localUser = CurrentUserHolder.getUser();System.out.println(localUser);//输出:User(name=小黑子, age=18)//用户操作完成后,可以remove掉CurrentUserHolder.clearUser();}
}

ps:由于ThreadLocal是基于线程的,所以在不同的线程中,通过ThreadLocal获取的用户信息是独立的,这在多线程环境下非常有用,可以避免线程之间的数据混乱和冲突。

三、ThreadLocal的实现原理

直接上图!下图中基本描述出了ThreadThreadLocalMapThreadLocal三者之间的关系。

解释一下:

  • ThreadLocal中用于保存线程的独有变量的数据结构是一个内部类:ThreadLocalMap,也是k-v结构,key就是当前ThreadLocal对象,value就是我们要保存的值。

  • Thread类中维护了两个ThreadLocalMap成员变量,threadLocals和inheritableThreadLocals,它们的默认值是null,类型为ThreadLocal.ThreadLocalMap,也就是ThreadLocal类的一个静态内部类ThreadLocalMap,感兴趣的可以去看一下源码。

四、核心源码

4.1 ThreadLocalMap内部类

在静态内部类ThreadLocalMap中,维护了一个数据结构类型为Entry的数组,源码如下:

static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}
}

从源码中我们可以看到,Entry继承了一个ThreadLocal类型的弱引用并将其作为key,value为Object类型(也就是我们需要保存的值)

我们再来看一下它的成员变量:

//数组的默认初始化容量
private static final int INITIAL_CAPACITY = 16;
//Entry数组,大小必须为2的幂
private Entry[] table;
//数组内部元素个数
private int size = 0;
//数组扩容阈值,默认为0,创建ThreadLocalMap对象后会被重新设置
private int threshold; 

是不是有点熟悉,这几个变量和HashMap中的变量很类似,功能也类似。

最后看一下它的构造方法:

/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
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);
}

注释翻译过来大概就是,该构造方法是懒加载的,只有我们创建一个Entry对象并需要放入到Entry数组的时候才会去初始化数组。

4.2 set()方法

接下来我们就介绍一下ThreadLocal常用的一些方法吧,首先看一下set()方法:

public void set(T value) {//获取当前线程Thread t = Thread.currentThread();//获取当前线程的ThreadLocalMap对象ThreadLocalMap map = getMap(t);if (map != null)// 如果map存在,则将当前ThreadLocal对象作为key,value作为value放入map中map.set(this, value);else// 如果map不存在,则创建一个新的ThreadLocalMap对象,并新建一个Entry放入该ThreadLocalMap, 调用set方法的ThreadLocal和传入的value作为该Entry的key和valuecreateMap(t, value);
}

解释:

  • 获取当前线程,拿到当前Thread的ThreadLocalMap对象。

  • 如果map存在,则将当前ThreadLocal对象作为key,value作为value放入map中。

  • 如果map不存在,则创建一个新的ThreadLocalMap对象,并新建一个Entry放入该ThreadLocalMap, 调用set方法的ThreadLocal和传入的value作为该Entry的key和value。

4.3 get()方法

源码如下:

public T get() {//获取当前线程Thread t = Thread.currentThread();//获取当前线程的ThreadLocalMap对象ThreadLocalMap map = getMap(t);if (map != null) {//map存在,通过this(当前ThreadLocal)获取EntryThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")//Entry不为空,返回该Entry的value值T result = (T)e.value;return result;}}//map不存在,调用setInitialValue()方法设置初始值return setInitialValue();
}

解释:

  • 通过当前线程获取ThreadLocalMap:

    • 如果map存在,则通过当前ThreadLocal获取对应的Entry,若Entry不为空,返回该Entry的value值。

    • 如果map不存在,则调用setInitialValue()方法设置初始值。

  • setInitialValue():

    • 根据initalValue()方法获取value值,默认值为null,可以重写该方法。

    • 通过当前线程获取ThreadLocalMap对象。

    • map存在,设置当前值为上述value,不存在则创建新的ThreadLocalMap,并将值设置为value。

4.4 remove()方法

源码如下:

public void remove() {//根据当前线程获取ThreadLocalMap对象ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)//存在,执行remove方法m.remove(this);
}

解释:

  • 根据当前线程获取ThreadLocalMap对象,存在则执行remove()方法。remove(this)方法中,将ThreadLocal作为key来删除对应的Entry。

五、内存泄漏问题

5.1 分析

读到这,相信你对ThreadLocal的基本原理有了更深一步的理解,我们把上图补全,从堆栈视角看一下它们之间的引用关系。

我们可以看到,ThreadLocal对象,有两个引用,一个是栈上的ThreadLocal引用,一个是ThreadLocalMap中Key对它的引用。如果栈上的ThreadLocal引用不再使用了,那么ThreadLocal对象因为还有一条引用链在,所以会导致它无法回收,久而久之就会OOM。

这就是我们所说的ThreadLocal的内存泄漏问题,为了解决这个问题,ThreadLocalMap使用了弱引用,就是上述我们说过的Entry数组:

static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}
}

可以看出,ThreadLocal的引用k通过构造方法传递给了Entry类的父类WeakReference的构造方法,那么可以理解为ThreadLocalMap中的键是ThreadLocal的弱引用。

穿插一下Java中的四大引用:

  • 强引用:Java中默认的引用类型,只要引用还存在,即便OOM也不会被回收。

  • 软引用:内存不足时,将会被干掉。

  • 弱引用:无论内存充足与否,只要执行GC,就会被干掉。

  • 虚引用:最弱的一种引用,存在意义就是为了将关联虚引用的对象在被GC掉之后收到一个通知。

如果用了弱引用,那么ThreadLocal对象就可以在下次GC的时候被回收掉了。

这样做可以很大程度上避免了因为ThreadLocal的使用而导致的OOM问题,但也无法彻底避免

我们可以看到,虽然key是弱引用,但是value是强引用,而且它的生命周期是和Thread一样的,也就是说,只要Thread还在,那么这个对象就无法被回收。

那么,什么情况下,Thread会一直在呢,那就是线程池,这就导致value一直无法被回收。

5.2 如何解决

ThreadLocalMap底层使用数组来保存元素,使用“线性探测法”来解决hash冲突,在每次调用ThreadLocal的get、set、remove方法时,内部会实际调用ThreadLocalMap的get、set、remove等操作,而ThreaLocalMap的每次set、get、remove时,都会对key为null的Entry进行清除(expungeStateEntry()方法,将Entry的value清空,等下次GC就会被回收)。

所以,当我们一个ThreadLocal用完后,就手动remove一下,就可以在下次GC时,把Entry清理掉。

5.3 总结

上述我们分了两种情况来看ThreadLocal内存泄漏问题:

  • key使用强引用:引用ThreadLocal的对象被回收了,但是ThreadLocalMap持有ThreadLocal的强引用,如果没有手动remove,ThreadLocal不会被回收,导致Entry内存泄漏。

  • key使用弱引用:引用ThreadLocal被回收,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动remove,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set、get、remove的时候会被清除。

比较两种情况,我们可以发现:由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动remove,就会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal被清理后key为null,对应的value在下一次ThreadLocalMap调用set、get、remove的时候可能会被清除。

因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期和Thread一样长,如果没有手动remove就会导致内存泄漏,而不是因为弱引用。

End:希望对大家有所帮助,如果有纰漏或者更好的想法,请您一定不要吝啬你的赐教🙋。

相关文章:

Java并发编程第8讲——ThreadLocal详解

ThreadLocal无论是在项目开发还是面试中都会经常碰到&#xff0c;它的重要性可见一斑&#xff0c;本篇文章就从ThreadLocal的使用、实现原理、核心方法的源码、内存泄漏问题等展开介绍一下。 一、什么是ThreadLocal ThreadLocal是java.lang下面的一个类&#xff0c;在JDK 1.2版…...

2023复旦大学计算机科学技术(网络空间安全)保研记录

BG&#xff1a;中九rank前5&#xff05;、科研经历少、无竞赛 复旦大学计算机科学与技术--网络空间安全方向&#xff0c;参营4天&#xff08;6.26-6.29&#xff09;&#xff0c;管午饭&#xff0c;住宿自理 6.26--报道听会&#xff0c;6.27--听会&#xff0b;实验室参观 给了…...

linux系统通过docker安装python的jieba,如何找到jieba路径替换分词文件

1、安装python镜像 python镜像名为 jetz_python3.7.131、进入容器 首次安装镜像后,容器启动,进入容器中,其中py37是容器名称,后面会一直用到 docker run -it --name py37 jetz_python3.7.13 /bin/bash如果进入过容器退出了,而容器已存在,上面的的 命令会报错,直接根…...

Python Functions-函数

目录 创建函数 调用函数 参数还是自变量&#xff1f; 参数数量 任意参数&#xff0c;*args 关键字参数 任意关键字参数&#xff0c;**kwargs 默认参数值 将列表作为参数传递 The pass Statement 递归 函数是一个只有在被调用时才运行的代码块。 可以将称为参数的数…...

【人工智能】机器学习的入门与提升

目录 1.入门 1.1.从何处开始 1.2.数据集 1.3.数据类型 2.平均中位数模式 2.1.均值、中值和众数 2.2.均值 2.2.1.实例 2.2.2.运行结果 2.3.中值 2.3.1.实例 2.3.2.运行结果 2.3.3.实例 2.3.4.运行结果 2.4.众数 2.4.1.实例 2.4.2.运行结果 2.5.章节总结 3.标准…...

WEB漏洞原理之---【XMLXXE利用检测绕过】

文章目录 1、概述1.1、XML概念1.2、XML与HTML的主要差异1.3、XML代码示例 2、靶场演示2.1、Pikachu靶场--XML数据传输测试玩法-1-读取文件玩法-2-内网探针或攻击内网应用&#xff08;触发漏洞地址&#xff09;玩法-3-RCE引入外部实体DTD无回显-读取文件开启phpstudy--apache日志…...

element-table排序icon没有点亮

<el-table :data"tableData" ref"tableRef"border :sort"defaultSort":default-sort"defaultSort"><el-table-column sortable :sort-orders"sortOrder" prop"date" label"日期"> </el-…...

传统的经典问题 Java 的 Interface 是干什么的

传统的经典问题 Java 的 Interface 是干什么 解答 上面的这个问题应该还是比较好回答的吧。 只要你做过 Java &#xff0c;通常 Interface 的问题多多少少会遇到&#xff0c;而且可能会遇到一大堆。 在JAVA编程语言中是一个抽象类型&#xff08;Abstract Type&#xff09;&…...

Linux 文件 目录管理

Linux 文件 基本属性 Linux 系统是一种典型的多用户系统&#xff0c;为了保护系统的安全性&#xff0c;不同的用户拥有不同的地位和权限。Linux 系统对不同的用户访问同一文件&#xff08;包括目录文件&#xff09;的权限做了不同的规定。 可以使用命令&#xff1a;ll 或 ls –…...

QT信号槽实现原理

定义Q_OBJECT宏 在宏中声明了几个重要的成员变量及成员函数&#xff0c;包括声明了一个只读的静态成员变量static MetaObject&#xff0c;以及3个public的成员函数 static const QMetaObject staticMetaObject; virtual const QMetaObject *metaObject() const; virtual void …...

7-7 求鸡兔数量

老张家养了很多鸡和兔&#xff0c;圈养在一个笼子里&#xff0c;清早起来老张站在笼子旁边数了数头的个数&#xff0c;蹲下来又数了数脚的个数&#xff0c;你能帮他快速算出来鸡兔各有多少只吗&#xff1f;如实在算不出来&#xff0c; 就提示“error” 输入格式: 输入头的个数…...

CTF 全讲解:[SWPUCTF 2022 新生赛]webdog1__start

文章目录 参考环境题目learning.php信息收集isset()GET 请求查询字符串全局变量 $_GET MD5 绕过MD5韧性脆弱性 md5()弱比较隐式类型转换字符串连接数学运算布尔判断 相等运算符 MD5 绕过科学计数法前缀 0E 与 0e绕过 start.php信息收集头部检索 f14g.php信息收集 探秘 F1l1l1l1…...

聊天机器人

收集窗帘相关的数据 可以用gpt生成&#xff0c;也可以用爬虫 图形化界面 gradio 向量数据库 faiss python代码 import gradio as gr import random import timefrom typing import Listfrom langchain.embeddings.openai import OpenAIEmbeddings from langchain.vectorstor…...

肖sir__mysql之综合题练习__013

数据库题&#xff08;10*5&#xff09; 下面是一个学生与课程的数据库&#xff0c;三个关系表为&#xff1a; 学生表S&#xff08;Sid&#xff0c;SNAME,AGE,SEX&#xff09; 成绩表SC&#xff08;Sid&#xff0c;Cid&#xff0c;GRADE&#xff09; 课程表C&#xff08;Cid&…...

阿里云服务器部署安装hadoop与elasticsearch踩坑笔记

2023-09-12 14:00——2023.09.13 20:06 目录 00、软件版本 01、阿里云服务器部署hadoop 1.1、修改四个配置文件 1.1.1、core-site.xml 1.1.2、hdfs-site.xml 1.1.3、mapred-site.xml 1.1.4、yarn-site.xml 1.2、修改系统/etc/hosts文件与系统变量 1.2.1、修改主机名解…...

Golang 中 int 类型和字符串类型如何相互转换?

在日常开发中&#xff0c;经常需要将数字转换为字符串或者将字符串转换为数字。在 Golang 中&#xff0c;有一些很简便的方法可以实现这个功能&#xff0c;接下来就详细讲解一下如何实现 int 类型和字符串类型之间的互相转换。 使用 strconv 包 strconv 包提供的 Itoa 和 Ato…...

**20.迭代器模式(Iterator)

意图&#xff1a;提供一种方法顺序访问一个聚合对象中的各个元素&#xff0c;而又不需要暴露该对象的内部表示。 上下文&#xff1a;集合对象内部结构常常变化各异。对于这些集合对象&#xff0c;能否在不暴露其内部结构的同时&#xff0c;让外部Client透明地访问其中包含的元素…...

计算机视觉与深度学习 | 视觉里程计理论

===================================================== github:https://github.com/MichaelBeechan CSDN:https://blog.csdn.net/u011344545 ===================================================== 视觉里程计理论基础 1 、立体视觉中的三维测量及几何约束2 、立体视觉匹…...

Go网络请求中配置代理

如何配置代理 不配置代理&#xff0c;本地请求google等会超时 package mainimport ( "fmt" "net/http" "time")func main() { // 创建一个自定义的 Transport 实例 //transport : &http.Transport{ // Proxy: func(req *http.Request) (*url…...

【ArcGIS】基本概念-矢量空间分析

栅格数据与矢量数据 1.1 栅格数据 栅格图是一个规则的阵列&#xff0c;包含着一定数量的像元或者栅格 常用的栅格图格式有&#xff1a;tif&#xff0c;png&#xff0c;jpeg/jpg等 1.2 矢量数据 矢量图是由一组描述点、线、面&#xff0c;以及它们的色彩、位置的数据&#x…...

从零到跑通:Windows下OTB100数据集与Matlab评测环境保姆级避坑指南

从零到跑通&#xff1a;Windows下OTB100数据集与Matlab评测环境保姆级避坑指南 刚接触目标跟踪领域的研究者&#xff0c;往往需要从经典数据集评测开始。OTB&#xff08;Object Tracking Benchmark&#xff09;作为目标跟踪领域的基石数据集&#xff0c;包含100个具有挑战性的视…...

HubSpot如何通过联盟计划快速增长?内容驱动型联盟营销的成功案例解析

在 SaaS 获客成本&#xff08;CAC&#xff09;不断攀升的今天&#xff0c;HubSpot 的增长奇迹始终是行业研究的焦点。除了教科书级的「集客营销&#xff08;Inbound Marketing&#xff09;」&#xff0c;其 HubSpot Affiliate Program&#xff08;联盟营销计划&#xff09;更是…...

2026AI大模型接口聚合站榜单揭晓!这些平台助你一站式解决模型调用难题

跨国网络延迟、复杂的支付方式以及分散的接口协议&#xff0c;常常让开发者在调用AI大模型API时体验不佳。而AI大模型接口聚合站就像一个智能中转平台&#xff0c;能让调用AI大模型API变得像调用本地服务一样简单。通过API聚合站&#xff0c;开发者可以一站式解决国内外主流AI模…...

自我提升智能体的自进化原理和实践

自我提升智能体skill赋予了AI助手从错误中反思、学习并自动繁衍新通用技能的持续进化能力。 1 实际案例 帮我运行测试,看看为什么登录模块失败。 流程如下: 第一步,任务开始前,Hook 触发 activator.sh(通过 UserPromptSubmit 触发)。它不会输出一大堆规则,只是提醒 AI 一…...

RevokeMsgPatcher:微信/QQ/TIM防撤回补丁工具完全指南

RevokeMsgPatcher&#xff1a;微信/QQ/TIM防撤回补丁工具完全指南 【免费下载链接】RevokeMsgPatcher :trollface: A hex editor for WeChat/QQ/TIM - PC版微信/QQ/TIM防撤回补丁&#xff08;我已经看到了&#xff0c;撤回也没用了&#xff09; 项目地址: https://gitcode.co…...

如何开始嵌入式Linux的学习呢?

如何开始嵌入式Linux的学习呢&#xff1f; &#xff08;又名&#xff1a;Imx-forge上手Roadmap&#xff09; 我昨天一下班就回去看了一下仓库&#xff0c;的确太乱&#xff0c;而且mkdocs工具日益陷入停滞维护&#xff0c;所以我们转网站啦&#xff01; 我本来打算直接画一个…...

产品竞争策略方法论:构建“差异化 + 结构化 + 系统化”的竞争优势

目录 一、问题与背景 二、本文将系统讲解 三、产品竞争的本质与底层逻辑 3.1 竞争的本质 3.2 竞争的三层结构(必须理解) 3.3 IoT竞争的特殊性 四、IoT产品竞争结构模型(核心框架) 4.1 五层竞争模型(核心体系) 4.2 竞争演进路径 五、五大竞争策略模型(核心方法…...

一键转载革命:auto_feed_js如何让PT资源分享效率提升10倍

一键转载革命&#xff1a;auto_feed_js如何让PT资源分享效率提升10倍 【免费下载链接】auto_feed_js PT站一键转载脚本 项目地址: https://gitcode.com/gh_mirrors/au/auto_feed_js 在PT&#xff08;Private Tracker&#xff09;社区中&#xff0c;资源分享是一项既重要…...

2026年项目管理工具选型指南:主流方案对比与Gitee核心优势解析

在数字化转型深入与研发效能要求不断提升的2026年&#xff0c;选择一款适配团队基因、能够无缝衔接管理与开发流程的项目管理工具&#xff0c;已成为企业提升协作效率、保障项目交付的关键。面对市场上从轻量级协作到重型研发管理的各类方案&#xff0c;企业选型往往面临工具割…...

FastDFS整合Nginx踩坑记:升级1.22.0修复CVE-2021-23017,如何平滑保留模块不报错?

FastDFS整合Nginx安全升级实战&#xff1a;从漏洞修复到模块兼容的全流程指南 最近在维护一个使用FastDFS作为分布式存储的生产环境时&#xff0c;遇到了Nginx的CVE-2021-23017安全漏洞问题。这个漏洞可能允许攻击者通过特制的DNS响应导致工作进程崩溃&#xff0c;对于线上业务…...