【从零开始学习Java重要集合】深入解读ThreadLocal类

news2024/9/22 7:40:55

目录

前言: 

ThreadLocal: 

ThreadLocal的内部结构: 

 ThreadLocal的常用方法:

1.set方法:

2.get方法:

3.setInitialValue方法

remove方法():

ThreadLocalMap:​编辑

成员变量:

存储结构:

 高频考点:

总结: 


 

前言: 

        当我们编写多线程程序时,经常会遇到一些需要在线程之间共享数据的情况。然而,共享数据可能会引发线程安全的问题,例如竞态条件(race condition)和数据覆盖等。为了解决这些问题,Java 提供了许多线程同步的机制,如 synchronized 关键字和 Lock 接口等。

然而,并不是所有的场景都适合使用传统的线程同步方式。有些情况下,我们更希望每个线程都拥有自己独立的数据副本,以避免线程之间的干扰。这时,ThreadLocal 就成为了一个非常有用的工具。

ThreadLocal: 

ThreadLocal 是 Java 提供的一个线程本地变量工具类,它为每个线程提供了一个独立的变量副本,使得每个线程都可以独立地操作自己的变量副本,而不会影响其他线程。简单来说,ThreadLocal 可以理解为一个以线程为 key、以变量为 value 的存储结构。这种变量在线程的生命周期内起作用。

总结:

        ThreadLocal可以在多线程环境下为线程创建变量副本,使得变量副本只存在于当前线程当中。而且是存在线程隔离的,每一个线程的变量副本都是相互隔离的,不会彼此影响。线程变量副本的存在缓解了线程在跨函数使用变量时的繁琐,并且可以为每一个线程单独记录数据

举例:假设我们有一个用户管理后端,而我们在查询的时候,需要记录当前使用者的查询次数。那么就会出现一个问题:如果我们只是简单的创建一个变量记录当前查询者的查询次数的话,那么所有的使用者的查询次数都会记录到这个变量当中,无法满足我们想要的记录当前使用者的查询次数,因为在代码中创建的这个变量是被所有线程所共享的。

那么在这个时候我们就需要使用ThreadLocal。创建一个为当前线程所独享的变量。通过这种方式,我们就使得每一个用户都有了自己的一个查询计数器。

最后我们来看一看源代码中的作者是如何描述ThreadLocal的作用的:

ThreadLocal的内部结构: 

早期设计:

        在JDK早期版本的时候,ThreadLocal的设计方案为:每一个ThreadLocal都创建一个Map,然后用线程作为MapKey,要存储的局部变量作为对应的value。这样就达到了各个线程的局部变量隔离的效果:

现在设计:

      每一个Thread维护一个ThredLocalMap,这个MapkeyThreadLocal本身,value才是真正要存储的值。

换句话来说:

       (1) 每一个Thread线程内部都有一个Map

       (2)  Map里面存储ThreadLocal对象(Key)线程的变量副本(Vlaue)

       (3) Thread内部的map是由ThreadLLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。

 ThreadLocal的常用方法:

方法声明描述
ThreadLocal()创建ThreadLocal对象
set()设置当前线程的变量副本
get()获取当前线程的变量副本
remove()移除当前线程的变量副本

1.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);
        }
    }

set方法的整体逻辑为:先获取到当前线程,再根据当前线程获取到对应的ThreadLocalMap,如果map不为空就进行插入,如果map为空的话就创建一个map并且插入t和value。

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

get方法的整体逻辑为:先获取到当前线程,再根据当前线程获取到对应的ThreadLocalMap。如果map不为空的话,就根据当前的ThreadLocal为Key,调用getEntry获取到对应的存储实体e,如果e不为空的话,就获取到e所对应的value值。如果获取到的map为空的话,就执行初始化。

3.setInitialValue方法

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }

setInitialValue方法的整体逻辑为:获取到value值(这里使用的initialValue默认返回null),获取到当前线程。之后再根据当前线程查询是否有对应的map。如果当前线程有对应的map,那么就更新值。否则的话就进行创建。

最后instance of 关键字用来判断this的类型是否属于TerminatingThreadLocal。如果this属于是erminatingThreadLocal类型的,那么就调用register方法将this进行注册到TerminatingThreadLocal类中。

总结来说,最后的这段代码用于将终止类型的 ThreadLocal 实例注册到 TerminatingThreadLocal 类的静态列表中。这样,在线程退出时,终止类型的 ThreadLocal 实例会自动从 ThreadLocalMap 中移除,避免内存泄漏。

当一个线程终止时,JVM会自动调用Thread类中的ThreadLocal.ThreadLocalMap.threadTerminated()方法,该方法会遍历所有的ThreadLocal对象,并调用TerminatingThreadLocal类的terminate()方法。terminate()方法将删除该线程所有使用的TerminatingThreadLocal对象所对应的本地变量,从而释放内存。这种方式比较安全和可靠,因为即使用户没有手动清理线程本地变量,也不会造成内存泄漏。

remove方法():

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null) {
             m.remove(this);
         }
     }

 它调用了threadmap的remove方法,因此我们转到这个方法里面:

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

remove方法的整体逻辑为:根据ThreadLocal对象的哈希码,在ThreadLocalMap中定位并移除对应的Entry对象,以实现对ThreadLocal对象的移除操作。

其实通过这么多的源码分析我们能够看出:想要深入了解ThreadLocal,就一定要深入了解ThreadLocalMap。因此我们来介绍一下ThreadLocalMap 

ThreadLocalMap:

部分源码截取:

由此我们可以看出:ThreadLocalMap并没有实现Map接口。

需要注意的是:ThreadLocalMap虽然是ThreadLocal 的内部类,但是其实例却是由Thread对象维护的。

成员变量:

        /**
         * 初始容量 -- 必须是2的整次幂.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

        /**
         * 当前表中enrtys的个数.
         */
        private int size = 0;

        /**
         * 扩容阈值.
         */
        private int threshold; // Default to 0

在这里数组长度也必须是二的n次幂。道理和HashMap篇讲的一样:

我们在使用remove方法的时候使用了哈希码和数组长度-1进行&运算。本来是哈希码%数组长度。但是为了提高效率我们采用位运算,如果想要哈希码和数组长度-1进行&运算的结果和哈希码%数组的结果一致,我们就需要让数组的长度等于二的n次幂。

更详细的解释可以看我HashMap的文章:

【从零开始学习JAVA集合 | 第一篇】深入解读HashMap源码(含面试题)-CSDN博客icon-default.png?t=N7T8https://liyuanxin.blog.csdn.net/article/details/134867511

存储结构:

 Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }

 高频考点:

1.ThreadLocal为什么会出现内存泄漏?

当Thread是线程池中的一个对象的时候就会发生内存泄漏。这是因为当ThreadLocal对象使用完之后,本来应该要对Entry对象进行回收。但是线程池中的线程不会被销毁,线程对象是通过强引用指向ThreadLocalMap的,ThreadLocalMap也是通过强引用指向Entry对象的,因此线程不被回收。Entry对象也不会被回收,这就出现了内存泄漏。

基于这种不稳定性,我们设置Entry中的Key(ThreadLocal)为弱引用,在下一次垃圾回收的时候回收掉这个Key。此时Key就为null了,而ThreadLocalMap的set/get/remove在检测到key==null的时候,会自动的把value清空,这样就实现了避免内存泄漏。

2.ThreadLocal的使用场景?

 1.Java中有的对象在被多个线程同时操作的时候就会报错,例如SimpleDateFormat对象。这个时候我们就可以使用ThreadLocal来为每一个线程都创建一个ThreadLocal对象。

2.全局存储用户信息,比如用户ID。我们就可以将其放到ThreadLocal对象当中,被线程独享。

其实我认为:ThreadLocal的作用不是去保护共享数据的安全性,而是去做数据的隔离。因此他和加锁关键字synchronized 并没有什么可比的。因为synchronized是做共享数据的安全性的。 

总结: 

        总的来说,ThreadLocal是Java中的一个线程局部变量,它为每个线程提供了一个独立的变量副本,保证了线程安全。ThreadLocal可以用于管理线程私有的数据,避免竞争条件,提高系统的并发性能。在实际应用中,ThreadLocal常用于数据库连接管理、用户登录信息管理、线程调试信息传递、消息传递等场景。

需要注意的是,虽然ThreadLocal可以有效地解决线程安全问题,但是过度使用ThreadLocal也可能会导致内存泄漏问题。因此,在使用ThreadLocal时,需要特别注意及时清理ThreadLocal中的数据,避免不必要的内存占用。

此外,还需要注意ThreadLocal的使用方式,尽量避免在多个线程之间共享ThreadLocal实例,以免出现意外的问题。同时,也应该考虑使用InheritableThreadLocal子类,以便在子线程中继承父线程的数据。

如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,大家的支持就是我坚持下去的动力!

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

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

相关文章

Kubernetes 集群管理—日志架构

日志架构 应用日志可以让你了解应用内部的运行状况。日志对调试问题和监控集群活动非常有用。 大部分现代化应用都有某种日志记录机制。同样地&#xff0c;容器引擎也被设计成支持日志记录。 针对容器化应用&#xff0c;最简单且最广泛采用的日志记录方式就是写入标准输出和标…

stm32 - 基础架构

stm32 - 基础架构 基础架构外设概念系统结构引脚定义晶振工程 基础架构 外设概念 NVIC &#xff08;内核外设&#xff09; SysTick &#xff08;内核外设&#xff09; 其他是片上外设 系统结构 内核引出三条总线 ICode 指令总线&#xff1a; 连接Flash闪存&#xff08;编写的…

数据结构与算法之美学习笔记:47 | 向量空间:如何实现一个简单的音乐推荐系统?

这里写自定义目录标题 前言算法解析总结引申 前言 本节课程思维导图&#xff1a; 很多人都喜爱听歌&#xff0c;以前我们用 MP3 听歌&#xff0c;现在直接通过音乐 App 在线就能听歌。而且&#xff0c;各种音乐 App 的功能越来越强大&#xff0c;不仅可以自己选歌听&#xff0…

【Docker】Linux中Docker镜像结构及自定义镜像,并且上传仓库可提供使用

目录 一、镜像结构 1. 基本结构 2. 常用命令 二、自定义镜像 1. 基本镜像 2. 进阶镜像 3. 完善镜像 三、镜像上传仓库 每篇一获 一、镜像结构 自定义 Docker 镜像有很多用途&#xff0c;以下是一些主要的应用场景&#xff1a; 一致性环境&#xff1a;通过自定义镜像&a…

PPT文档怎么转换PDF?一个方法教你快速实现

在我们的办公、学习中难免会遇到需要将ppt转pdf文件的需求。现在的网络中有各种各样的PDF转换工具&#xff0c;有些操作很复杂&#xff0c;有些需要下载软件非常麻烦。接下来&#xff0c;给大家分享一款草最简单还不用下载软件的PPT转PDF&#xff08;https://www.yasuotu.com/p…

黑马程序员JavaWeb开发|案例:tlias智能学习辅助系统(1)准备工作、部门管理

一、准备工作 1.明确需求 根据产品经理绘制的页面原型&#xff0c;对部门和员工进行相应的增删改查操作。 2.环境搭建 将使用相同配置的不同项目作为Module放入同一Project&#xff0c;以提高相同配置的复用性。 准备数据库表&#xff08;dept, emp&#xff09; 资料中包含…

C#基础-空处理

在c#中&#xff0c;值对象是没有办法赋值为null的。比如说&#xff0c;你想要定义一个布尔值&#xff0c;你的赋值数据要么得是true、要么就得是false&#xff0c;默认情况下我们永远没可能给这个布尔赋值为null&#xff0c;即使只是对这个变量进行声明而不初始化数据&#xff…

代码随想录算法训练营第四天| 24. 两两交换链表中的节点、19.删除链表的倒数第N个节点面试题 02.07. 链表相交、142.环形链表II

文档讲解&#xff1a;虚拟头节点&#xff0c;三指针&#xff0c;快慢指针&#xff0c;链表相交&#xff0c;环形链表&#xff0c; 技巧&#xff1a; 1、对于指针的操作要画图&#xff0c;明确步骤后好做了 2、使用虚拟头节点可以避免对头节点单独讨论&#xff0c;且方便对头节点…

详解CAS及ABA问题

&#x1f308;&#x1f308;&#x1f308;今天给大家分享的是 CAS 问题。 清风的CSDN博客 &#x1f6e9;️&#x1f6e9;️&#x1f6e9;️希望我的文章能对你有所帮助&#xff0c;有不足的地方还请各位看官多多指教&#xff0c;大家一起学习交流&#xff01; ✈️✈️✈️动动…

【工具栏】SequenceDiagram插件的使用(根据代码生成时序图)

1. 安装 2.使用 进入代码页面&#xff0c;点击鼠标右键 选择方法 根据方法中的代码生成时序图

【LangChain学习之旅】—(8) 输出解析:用OutputParser生成鲜花推荐列表

【LangChain学习之旅】—&#xff08;8&#xff09; 输出解析&#xff1a;用OutputParser生成鲜花推荐列表 LangChain 中的输出解析器Pydantic&#xff08;JSON&#xff09;解析器实战第一步&#xff1a;创建模型实例第二步&#xff1a;定义输出数据的格式第三步&#xff1a;创…

AI-数学-高中-4.函数表达式特性-要变一起变

求f(x):换元法&#xff1a;左边代换时&#xff0c;右边也要同时替换&#xff0c;原作者视频&#xff1a;函数】1引导课&#xff1a;高中为什么用f(x)_哔哩哔哩_bilibili 1.什么是函数&#xff1a;给定任意一个x&#xff0c;都有唯一确定的y与之对应&#xff0c;这种x与y的关系就…

猫头虎分享:探索TypeScript的世界 — TS基础入门 ‍

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 专栏链接&#xff1a; &#x1f517; 精选专栏&#xff1a; 《面试题大全》 — 面试准备的宝典&#xff01;《IDEA开发秘籍》 — 提升你的IDEA技能&#xff01;《100天精通Golang》…

UML-类图和类图转化为代码

提示&#xff1a;文章详细的讲解了类图的四种关系&#xff0c;以及每种关系如何转化为对应的代码。 UML-类图和类图转化为代码 一、类于类之间的关系1.依赖关系2.关联关系(1) 单向关联(2) 双向关联(3) 自关联(4) 聚合关联(has-a)(5) 组合关联&#xff08;contains-a&#xff09…

当浏览器输入url的时候会发生什么?

说在前面 当我们在浏览器中输入URL并按下回车时&#xff0c;背后发生了一系列神秘的操作。本文将带您深入了解&#xff0c;从URL解析到页面渲染&#xff0c;揭秘浏览器输入URL的完整流程。 具体步骤 当浏览器输入URL时&#xff0c;一般经过以下细节步骤&#xff1a; 1、引言 …

【GDAL】Windows下VS+GDAL开发环境搭建

Step.0 环境说明&#xff08;vs版本&#xff0c;CMake版本&#xff09; 本地的IDE环境是vs2022&#xff0c;安装的CMake版本是3.25.1。 Step.1 下载GDAL和依赖的组件 编译gdal之前需要安装gdal依赖的组件&#xff0c;gdal所依赖的组件可以在官网文档找到&#xff0c;可以根据…

【学习心得】Git深入学习

若您还未安装Git或是只想简单使用&#xff0c;可以先看看我的文章“Git快速上手”http://t.csdnimg.cn/gsaGj 一、深入学习Git必须熟悉两个概念 &#xff08;1&#xff09;【四个区】Git本地有三个区&#xff0c;远程仓库也可以看出成一个区域 工作区、暂存区、本地仓库、远…

AI教我学编程之C#变量及实例演示

前言 在AI教我学编程之AI自刀 这篇文章中&#xff0c;我们知道了变量的基础类型&#xff0c;那么变量在C#中有什么作用呢&#xff1f;我们一起来看看吧&#xff01; 目录 重点先知 变量 变量类型 实例演示 变量声明 实例演示 提出疑问 初始化变量 自动初始化 多变量声明 实…

关于浏览器缓存的一些坑记录

1.js强制刷新浏览器缓存 网络上一搜基本都说用location.reload(true)可以强制刷新缓存&#xff0c;但是没人讲这个方法只准对火狐浏览器生效,火狐浏览器的确可以实现强制刷新并每次请求都是新的没有走缓存&#xff1b;chrome依然是走200 状态 缓存。 MDN解析&#xff1a; 2.m…

Python教程(23)——Python类中常用的特殊成员

在Python中&#xff0c;类特殊成员是指以双下划线开头和结尾的属性和方法&#xff0c;也被称为魔术方法&#xff08;Magic methods&#xff09;或特殊方法&#xff08;Special methods&#xff09;。这些特殊成员在类的定义中具有特殊的语法和功能&#xff0c;用于实现对象的特…