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

目录

前言: 

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.mfbz.cn/a/318030.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第十二章 Java内存模型与线程(一)

文章目录 12.3 Java内存模型12.3.1 主内存与工作内存12.3.2 内存间交互操作小结12.3.3 对于volatile型变量的特殊规则12.3.5 原子性、可见性与有序性12.3.6 先行发生原则 12.3 Java内存模型 12.3.1 主内存与工作内存 1.Java 内存模型规定了所有的变量都存储在主内存&#xff…

Java反转单链表

/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val val; }* ListNode(int val, ListNode next) { this.val val; this.next next; }* }*/ //核心思想&#xff0c;利用cur和Curnex…

驱动树莓派直流电机(二)——python

一、安装spyder 我喜欢用spyder编写python 有两种方法安装spyder&#xff0c;第一种&#xff08;版本更新&#xff09;&#xff0c;但是我更细化第二种&#xff0c;简单 方法一&#xff1a;在ubuntu PC端输入如下代码 wait~~~~~~~~~~~ 安装的慢就用下面这个代码 或者设置切…

脱离于ASP.NET 和Visual Studio编辑Razor脚本

Razor Pad是一个编辑Razor脚本的工具&#xff0c;脱离于ASP.NET 和Visual Studio。 github地址&#xff1a;https://github.com/RazorPad/RazorPad 如果在编译源码时出现&#xff1a;签名时出错: 未能对 bin\Debug\app.publish\RazorPad.exe 签名。SignTool Error: No certifi…

JFinal学生信息管理系统

JFinal学生信息管理系统 项目地址&#xff1a;mendianyu/StudentManage: JFinal学生信息管理系统 (github.com) 环境介绍&#xff1a; IDE&#xff1a;IDEA 2021.2.3 jdk:1.8 maven:3.6.3 项目介绍&#xff1a;JFinal框架实现的学生信息管理系统&#xff0c;完成简单的学生信…

一文解析低代码平台

一、低代码概念 低代码开发平台是一种无需编码或者只需要少量代码即可快速生成应用程序的开发平台&#xff0c;通过可视化进行应用程序开发的方法&#xff0c;让不同经验水平的开发人员可以通过图形化的用户界面&#xff0c;使用拖拽组件和模型驱动的逻辑来创建网页和移动应用程…

【QT】多层QTreeWidget与QStackedWidget的关联操作

通过点击多层QTreeWidget来控制QStackedWidget中的page页面切换 treeWidget设计 treeWidget设计&#xff1a; // treeWidget设计ui->treeWidget->clear();ui->treeWidget->setColumnCount(1);//第一层QStringList l;l<<"管理系统";QTreeWid…

JavaScript基础(26)_dom增删改练习

<!DOCTYPE html> <html lang"zh"><head><meta charset"UTF-8"><title>DOM增删改练习</title><link rel"stylesheet" href"../browser_default_style/reset.css"><style>table {borde…

分布式事务:构建无障碍的云原生应用的完美解决方案

目录 一、前言 二、分布式事务概述 2.1 什么是分布式事务 2.2 分布式事务的挑战 2.3 分布式事务的分类 三、传统解决方案分析 3.1 两阶段提交协议&#xff08;2PC&#xff09; 3.2 三阶段提交协议&#xff08;3PC&#xff09; 3.3 补偿事务 3.4 其他传统解决方案 四…

Qt/QML编程学习之心得:slider(34)

滑条slider&#xff0c;有时也成为进度条progressbar&#xff0c;在GUI界面中也是经常用到的。 import QtQuick 2.9 import QtQuick.Controls 2.0 import QtQuick.Layouts 1.2ApplicationWindow {id:rootvisible: truewidth: 1920height: 720//title: qsTr("Hello World&q…

慕尼黑工业大学最新提出!单目实时密集建图的混合隐式场方法

作者&#xff1a;小柠檬 | 来源&#xff1a;3DCV 在公众号「3DCV」后台&#xff0c;回复「原论文」可获取论文pdf 我们提出了一种新颖的方法&#xff0c;它将基于深度学习的密集SLAM与神经隐式场相结合&#xff0c;实时生成密集地图&#xff0c;而无需像以前的方法那样依赖RGB-…

Vue的api接口封装以及使用说明、模块说明

在Api目录下面建立user.js&#xff0c;如果以后有不同的接口请求地址都可以单独创建不同的&#xff0c;目的是方便维护&#xff01; import request from /utils/request 这个代码是引入之前封装好的 request.js 文件&#xff0c;具体可以参考上门一篇文档 Vue的request.js模…

基于springboot生鲜交易系统源码和论文

首先,论文一开始便是清楚的论述了系统的研究内容。其次,剖析系统需求分析,弄明白“做什么”,分析包括业务分析和业务流程的分析以及用例分析,更进一步明确系统的需求。然后在明白了系统的需求基础上需要进一步地设计系统,主要包括软件架构模式、整体功能模块、数据库设计。本项…

Seata TC端协调全局事务

1、Seata server注册器 //来自RM分支事务注册 super.registerProcessor(MessageType.TYPE_BRANCH_REGISTER, onRequestProcessor, messageExecutor); //开启全局事务 super.registerProcessor(MessageType.TYPE_GLOBAL_BEGIN, onRequestProcessor, messageExecutor); //提交全…

ubuntu安装mysql(tar.xz)

0:本机Ubuntu的版本为 腾讯云 18.04 1&#xff1a;下载地址 MySQL &#xff1a;&#xff1a; 下载 MySQL 社区服务器 2&#xff1a;上传文件到服务器 3:解压 sudo sumv mysql-8.2.0-linux-glibc2.17-x86_64-minimal.tar.xz /usrtar -xvf mysql-8.2.0-linux-glibc2.17-x86_6…

谷歌最新医学领域LLM大模型:AMIE

2024年1月11日Google 研究院发布最新医疗大模型AMIE&#xff1a;用于诊断医学推理和对话的研究人工智能系统。 文章链接&#xff1a;Articulate Medical Intelligence Explorer (AMIE) giuthub&#xff1a;目前代码未开源 关于大模型之前有过一篇总结&#xff1a;大语言模型(L…

小程序基础学习(组件传参)

原理&#xff1a;通知在组件标签中传递参数已达到传参的目的 在组件的js的 properties中接受传递来的参数 然后在页面是展示这些数据 源码&#xff1a; <!--components/my-info/my-info.wxml--> <view class"title"> <text class"texts"&g…

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

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

GPUMD分子动力学模拟-学习与实践

GPUMD分子动力学模拟-学习与实践 【20220813-樊哲勇 |基于GPUMD程序包的机器学习势和分子动力学模拟】 https://www.bilibili.com/video/BV1cd4y1Z7zi?share_sourcecopy_web 纯GPU下的MD分子模型系统软件 https://github.com/brucefan1983/GPUMD 跟GPUMD对接的一些python程…

调试(c语言)

前言&#xff1a; 我们在写程序的时候可能多多少少都会出现一些bug&#xff0c;使我们的程序不能正常运行&#xff0c;所以为了更快更好的找到并修复bug&#xff0c;使这些问题迎刃而解&#xff0c;学习好如何调试代码是每个学习编程的人所必备的技能。 1. 什么是bug&#xf…
最新文章