ThreadLocal类详解

news2025/2/24 23:17:30

ThreadLocal类注释翻译

打开JDK中ThreadLocal类源码,翻译类上注释如下(提取重点部分):

每个访问ThreadLocal实例对象的线程都有其自己的关于ThreadLocal对象的变量副本(通过get和set方法),只要线程存活而且ThreadLocal对象也存活,则线程都保留着与ThreadLocal对象的变量副本的隐式引用。线程停止后,其对应的threadlocal变量副本会被垃圾回收。

上面的翻译其实就表达了一个意思,即每个线程有自己的ThreadLocal对象,且Thread对象对ThreadLocal对象进行了隐式引用。如何进行的隐式引用呢?继续往下研究。

Entry解析

Entry是ThreadLocalMap类的一个内部类,其类注释如下:

该类对其key值ThreadLocal对象进行了弱引用,空键(即entry.get()==null)意味着该键不再被引用,因此可以从表中删除该项。在接下来的代码中,这些条目被称为“过时条目”。

看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的弱引用。即Entry对象没有被GC回收,但是key值ThreadLocal对象可以被GC回收,此时这个Entry对象的key就是null,称为"过时条目"。

ThreadLocalMap解析

ThreadLocalMap是一个定制的哈希映射,仅适用于维护线程本地值。ThreadLocal没有对外获取ThreadLocalMap对象的操作。ThreadLocalMap类是私有的,允许在Thread类中声明字段。为了处理非常大和长期的使用,hash entries 对key值使用弱引用。由于使用了弱引用,所以只有当entries空间满后,才会删除过时的entries。

上面是ThreadLocalMap类注释翻译。从上面翻译可以看出,ThreadLocal中对Thread本地存值,其实都是存到了ThreadLocalMap中。
在Thread类中,声明了ThreadLocalMap的变量,阅读Thread类源码,可知变量为:

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

由注释可知,这个成员变量由ThreadLocal类维护。即threadLocals变量的值,在ThreadLocal类中赋予。

下面看ThreadLocalMap中table属性:

 private Entry[] table;

table属性存储了本地线程中key-value键值对数组,因为一个线程可以存放多个key-value,所以是数组形式。上面提到,Entry的key是ThreadLocal对象,所以,在Thread本地变量中,key就是ThreadLocal对象,value就是我们要存的值,而这个key-value对象又存放在ThreadLocalMap中的Entry对象中。在Thread类中,有ThreadLocalMap对象的属性,这样就可以获取到任意存放的本地变量。这样这个功能的逻辑就形成了闭环。

但是jdk在设计这个功能时有些特殊,甚至说有些别扭。因为我们在给一个线程对象存取本地变量时,都是通过ThreadLocal对象的get()和set()方法来进行操作的。而我们上面又提到,ThreadLocal是线程对象本地变量Map中的一个key。用key来操作Map,把自己当成key存到Map中,这个操作是不是很另类呢?

ThreadLocal类方法

initialValue()方法

 protected T initialValue() {
        return null;
    }

初始化值。用于没有给ThreadLocal对象调用set()方法赋值,而直接调用get()方法取值时进行返回。即创建了ThreadLocal对象后,没有给其set()值,则调用get()时返回initialValue()方法的值。这个方法一般在创建ThreadLocal时通过内部类方式进行实现。

get()方法

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();
    }

先看第一行,获取到当前线程,即谁调用ThreadLocal对象的get()方法,就获取到谁的线程,
第二行调用getMap()方法,参数是当前线程,看getMap()方法源码:

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

返回了当前线程的threadLocals属性,这个属性我们上面分析过,就是当前线程的ThreadLocalMap,而这个Map中存放了当前线程的本地变量。

然后,调用ThreadLocalMap的getEntry()方法,参数是this,即当前的ThreadLocal对象,以此为key,找对应value。

总体流程就是: ThreadLocal作为key,它的get()方法首先获取到当前线程,然后再获取到当前线程的本地变量Map,即ThreadLocalMap,然后在把ThreadLocal自己作为key,传入ThreadLocalMap的getEntry()方法中,获取对应的value。这个设计别扭吧。

set()方法

  public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

跟get()方法逻辑类似,获取到当前线程,然后获取到当前线程的ThreadLocalMap成员属性,然后调用Map的set方法,key就是ThreadLocal对象本身,value是参数设置的值。set()进去的key和value最终加入到Entry[] table中去。
在这里插入图片描述

关于ThreadLocal内存泄漏问题

Thread类里引用了ThreadLocalMap对象,ThreadLocalMap中Entry对象对ThreadLocal对象进行了弱引用。所以自始至终,Thread对象和ThreadLocal对象都没有发生直接关系。所以,ThreadLocal对象在出栈后,会被GC。此时,Entry中key为null。但是value还是有值的。所以,这个Entry就在内存中,无法获取了。这就是内存溢出问题。示意图如下:
在这里插入图片描述

那为什么Entry要设置成对ThreadLocal的弱引用呢?为何不设置成强引用呢?因为如果设置成弱引用,有可能发生内存溢出,如果设置成强引用,Entry[]不被GC,ThreadLocal就不会GC。假如ThreadLocal没用了,也不会被回收,肯定造成了内存溢出。
采用弱引用后,ThreadLocal可以被回收了,就进行回收,虽然有造成内存溢出可能,但是不是一定会造成内存溢出。而且在ThreadLocal对象的set()和get()方法中,会检查当前Thread的ThreadLocalMap中是否有key为null的Entry清除掉。
想要避免ThreadLocal内存溢出问题,就在程序中及时调用remove()方法,及时删除掉不用的Entry对象,这样就不会有内存溢出问题了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2582.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

嵌入式和单片机开发模式的区别

一、 开发模式 单片机开发多为裸机&#xff0c;程序规模小&#xff0c;多为单个程序员独立开发。有些复杂产品也会使用高端单片机如STM32之类&#xff0c;并使用RTOS(uCOS、freeRTOS等)。嵌入式开发几乎全部基于嵌入式操作系统&#xff0c;目前使用最多的是 linux 和Android。…

公众号搜题接口系统使用方法

公众号搜题接口系统使用方法 本平台优点&#xff1a;免费查题接口搭建 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a;题库后台http://daili.jueguangzhe.c…

html实现飞机小游戏(源码)

文章目录1.思路讲解1.1 游戏设计1.2 主界面1.3 倒计时进入游戏1.4 游戏效果1.3 游戏结束2.实现源码2.1 游戏动态效果2.2 游戏主代码2.3 源码目录源码下载作者&#xff1a;xcLeigh 文章说明 html实现飞机大战源码&#xff0c;酷炫的界面效果&#xff0c;有四款飞机大战背景&…

Elasticsearch:通过热、温、冷和冻结层管理数据自动化 — 无需编码!

如果你想完全按照本文标题的建议去做&#xff0c;那就别无所求。 这篇文章旨在指导如何使用 Kibana Dashboard 的 “堆栈管理&#xff08;Stack Management&#xff09;” 功能集通过热、温、冷和冻结层自动移动数据&#xff0c;而无需进行任何编码或执行命令行动作。 在下面的…

Cookie 和 Session

本文主要讲解一下 Cookie 和 Session 的关系和区别&#xff0c;大家都知道 Session 比 Cookie 安全&#xff0c;Session 是存储在服务器端的&#xff0c;Cookie 是存储在客户端的&#xff0c;然而更详细的说&#xff0c;恐怕就不太清楚了 文章目录1. 什么是 HTTP2. Cookie2.1 图…

​目标检测算法——YOLOv5/YOLOv7改进之结合Criss-Cross Attention

关注”PandaCVer“公众号 深度学习Tricks&#xff0c;第一时间送达 &#xff08;一&#xff09;前沿介绍 论文题目&#xff1a;CCNet: Criss-Cross Attention for Semantic Segmentation 论文地址&#xff1a;https://arxiv.org/pdf/1811.11721.pdf 代码地址&#xff1a;ht…

B树和B+树(平衡多路查找树)

文章目录为什么需要B树B 树的特点B树的查找B树的引入B树的删除链接&#xff1a;https://www.cs.usfca.edu/~galles/visualization/Algorithms.html 可以点击 Indexing 下的 B Trees 和 B Trees 去学习。 为什么需要B树 对 B 树的需求随着访问物理存储介质&#xff08;如硬盘&…

【Java】反射, 枚举,Lambda表达式

✨博客主页: 心荣~ ✨系列专栏:【Java SE】 ✨一句短话: 难在坚持,贵在坚持,成在坚持! 文章目录一. 反射1. 反射的概述2. 反射的使用2.1 反射常用的类2.2 通过反射获取Class对象2.3 获得Class类相关的方法2.4 使用反射创建实例对象2.5 使用反射获取实例对象中的构造方法2.6 通过…

Spring学习第1篇:学习spring必备的概念知识

大家家好&#xff0c;我是一名网络怪咖&#xff0c;北漂五年。相信大家和我一样&#xff0c;都有一个大厂梦&#xff0c;作为一名资深Java选手&#xff0c;深知Spring重要性&#xff0c;现在普遍都使用SpringBoot来开发&#xff0c;面试的时候SpringBoot原理也是经常会问到&…

纸牌博弈问题

纸牌博弈问题 作者&#xff1a;Grey 原文地址&#xff1a; 博客园&#xff1a;纸牌博弈问题 CSDN&#xff1a;纸牌博弈问题 题目描述 有一个整型数组 A&#xff0c;代表数值不同的纸牌排成一条线。玩家 a 和玩家 b 依次拿走每张纸牌&#xff0c; 规定玩家 a 先拿&#xff…

win11开机音效设置的方法

微软为win11重做了开机音效&#xff0c;与我们一直以来使用的开机音效不太一样&#xff0c;听起来很不舒服&#xff0c;因此我们可以通过设置开机音效的方法来修改它&#xff0c;只要在个性化设置中就可以找到了&#xff0c;下面一起来试试看吧。 win11开机音效怎么设置&#…

wordpress图片压缩插件-免费批量wordpress图片压缩

wordpress图片压缩插件&#xff0c;相信每个人都知道图片的太大会影响到网站的加载速度。过多的图像会对服务器产生相应的压力。导致网站打开会越来越慢。而图片也是会被搜索引擎收录的&#xff0c;可以在百度图片里面能搜索的到&#xff0c;也算是增加了网站的宣传力度。今天给…

(附源码)计算机毕业设计SSM基于微信平台的匿名电子投票系统

&#xff08;附源码&#xff09;计算机毕业设计SSM基于微信平台的匿名电子投票系统 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。…

【微信小程序支付功能】uniapp实现微信小程序支付功能

支付实现流程 首先前端写一个页面&#xff0c;简单说就是有一个输入支付金额的 然后有一个按钮&#xff0c;点击可以支付。 点击按钮后触发支付方法&#xff0c;就是我下面写的这些代码&#xff0c;复制就可以了。 然后先请求后端的一个方法&#xff0c;把你的价格还有openid之…

在Vue中使用Swiper轮播图、同时解决点击轮播图左右切换按钮不生效的问题、同时将轮播图抽离出为一个公共组件

轮播图左右的切换按钮、如果点击没有反应&#xff0c;控制台也没有报错。很大可能是版本问题。如果不指定版本信息、默认安装的是最新的版本。版本过高或者过低都有可能导致无效。目前兼容性和稳定性比较好的是&#xff1a;5.4.5。 官网地址&#xff1a;https://www.swiper.com…

【隧道应用-1】netsh端口映射内网

1、端口映射 是指将一台主机的内网&#xff08;LAN&#xff09;IP 地址映射成一个公网&#xff08;WAN&#xff09;IP 地址&#xff0c;当用户访问提供映射端口主机的某个端口时&#xff0c;服务器将请求转移到本地局域内部提供这种特定服务的主机&#xff1b;利用端口映射功能…

猿创征文|程序员的浪漫(代码猜诗词)

✅作者简介&#xff1a; 全栈领域新星创作者&#xff0c;阿里云专家博主&#xff0c;华为云云享专家博主&#xff0c;掘金后端评审团成员&#xff0c; &#x1f495;前言&#xff1a;在大众的认知里&#xff0c;程序员只是一群坐在电脑前熬夜敲代码的…

webrtc 笔记

webrtc主要步骤 navigator.mediaDevices.getUserMedia({audio:true,redio:true}) 获取用户的摄像头状态,返回媒体流,把媒体流赋给video的srcObject属性,就能在页面上展示自己的音视频 let peer new RTCPeerConnection(servers) 创建peer实例,通过这个实例的一系列方法实现p2p…

vue3 :一个实用的 vite + vue3 组件库脚手架工具

目录 1 组件库脚手架内容 2 组件库脚手架技术栈 3 使用说明 3.1 克隆代码到本地 3.2 安装依赖 3.3 本地开发 3.4 创建新组件 3.5 构建文档 3.6 构建 example 3.7 发布组件库 4 组件库命令说明 无论是 vue2 全家桶还是 vue3 vite TypeScript&#xff0c;组件库的使…

无需购买服务器,用cpolar发布本地web网站

随着互联网的快速发展&#xff0c;网络也成为我们生活中不可缺少的必要条件&#xff0c;为了能在互联网世界中有自己的一片天地&#xff0c;建立一个属于自己的网页就成为很多人的选择。但互联网行业作为资本密集的行业&#xff0c;委托别人建立一个像样的网站要花费不少&#…