ThreadLocal源码解析

文章目录

  • 一、概述
  • 二、get()方法
  • 三、set()方法
  • 四、可能导致的内存泄漏问题
  • 五、remove
  • 六、思考:为什么要将ThreadLocalMap的value设置为强引用?


一、概述

  ThreadLocal是线程私有的,独立初始化的变量副本。存放在和线程进行绑定的ThreadLocalMap中。ThreadLocalMap的内部类Entry,是一个键值对的结构,key是ThreadLocal对象,value是某个变量在某个时刻的副本。并且Entry继承了WeakReference类,使其key(ThreadLocal对象)成为弱引用,如果未正确使用remove方法,可能会导致内存泄漏问题。
在这里插入图片描述构造entry对象时,key使用父类的方法,被包装成弱引用。

  ThreadLocalMapThreadLocal的一个静态内部类:
在这里插入图片描述  但是其初始化的操作,是绑定在每个线程中的,作为线程对象的属性,随着线程对象的加载而初始化。
在这里插入图片描述  并且ThreadLocalMap的内部,是一个entry键值对数组的形式。也有初始容量和扩容机制。这一点和HashMap类似,区别在于,处理Hash冲突时,HashMap使用的是拉链法,也就是对于Hash值相同的key,会形成一条链表乃至树化。而ThreadLocalMap使用的是开放定址法中的线性探测再散列,即某一个key计算出的Hash值,该位置已经有了元素,则会沿着数组下标依次向后寻找空位。
在这里插入图片描述在这里插入图片描述线性探测再散列
  ThreadLocal通常会作为类的属性,并且用static关键字修饰。原因在于ThreadLocal需要从属于某个类,而不是具体的实例。


二、get()方法

  在get方法中,主要做了几件事:

  1. 获取ThreadLocalMap。
  2. ThreadLocalMap不为空,则获取ThreadLocalMap的Entry 对象,并且对象不为空,就返回该Entry 对象的value。
  3. ThreadLocalMap为空,就执行初始化操作。
    public T get() {
    		 //获取当前线程对象(调用的是本地方法)
        Thread t = Thread.currentThread();
        //根据线程对象,获取到与该线程一一对应的ThreadLocalMap(ThreadLocalMap 是线程对象的属性) 
        ThreadLocalMap map = getMap(t);
        //map第一次是为空的
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //执行初始化操作
        return setInitialValue();
    }

  执行初始化操作:

    private T setInitialValue() {
    		 //一、初始化value为null
        T value = initialValue();
        //获取当前线程对象
        Thread t = Thread.currentThread();
        //二、用当前线程对象,获取ThreadLocalMap 
        ThreadLocalMap map = getMap(t);
        //map不为空,就将当前的ThreadLocal对象作为key,null作为value,构造Entry对象。
        if (map != null)
            map.set(this, value);
        else
        		//三、否则初始化map
            createMap(t, value);
        //返回null
        return value;
    }

	 //一
    protected T initialValue() {
        return null;
    }
    
    //二
    ThreadLocalMap getMap(Thread t) {
    		 //ThreadLocal.ThreadLocalMap threadLocals = null;
        return t.threadLocals;
    }

	 //三
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

三、set()方法

  set方法和get方法大同小异,也是根据当前线程获取ThreadLocalMap ,然后判空,执行set操作还是初始化操作。

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
        	  //一、向ThreadLocalMap 的entry中插入元素的操作。
            map.set(this, value);
        else
            createMap(t, value);
    }

四、可能导致的内存泄漏问题

  先看一下这段代码,声明了一个线程池,以及LocalVariable 静态内部类,其中的成员变量是5M大的数组。并且还有一个ThreadLocal属性,将LocalVariable作为key包装成了弱引用。

public class ThreadLocalMemoryLeak {
    private static final int TASK_LOOP_SIZE = 500;

    /*线程池*/
    final static ThreadPoolExecutor poolExecutor
            = new ThreadPoolExecutor(5, 5, 1,
            TimeUnit.MINUTES,
            new LinkedBlockingQueue<>());

    static class LocalVariable {
        private byte[] a = new byte[1024 * 1024 * 5];/*5M大小的数组*/
    }

    ThreadLocal<LocalVariable> threadLocalLV;

    public static void main(String[] args) throws InterruptedException {
        SleepTools.ms(4000);
        for (int i = 0; i < TASK_LOOP_SIZE; ++i) {
            poolExecutor.execute(new Runnable() {
                public void run() {
                    SleepTools.ms(500);
//
//                    LocalVariable localVariable = new LocalVariable();
//
//
//                    ThreadLocalMemoryLeak oom = new ThreadLocalMemoryLeak();
//                    oom.threadLocalLV = new ThreadLocal<>();
//                    oom.threadLocalLV.set(new LocalVariable());
//
//                   oom.threadLocalLV.remove();

                    System.out.println("use local varaible");

                }
            });

            SleepTools.ms(100);
        }
        System.out.println("pool execute over");
    }

}

  如果没有加入ThreadLocal,而是仅仅在空跑,使用jvisualvm进行观察,看到的内存使用情况,是相对比较平稳的:
在这里插入图片描述  接着打开注释:

  ThreadLocalMemoryLeak oom = new ThreadLocalMemoryLeak();
  oom.threadLocalLV = new ThreadLocal<>();
  oom.threadLocalLV.set(new LocalVariable());

  发现在运行过程中,内存使用情况起伏明显,多次触发gc。
在这里插入图片描述  并且最终程序执行完成后,内存还处于较高的水平,也就是说明堆中还存在很多没有被回收的垃圾对象。
在这里插入图片描述  为什么和没有使用ThreadLocal之前,会有如此大的差距?原因在于,每次垃圾回收时,作为弱引用的Entry的key:ThreadLocal对象会被回收,但是其value没有被回收。(在JVM停止时统一销毁)。
在这里插入图片描述  而每个线程中都存在一个5M的强引用对象没有被回收。
  解决方式是,在ThreadLocal使用完成后,手动调用remove方法进行清除:

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

五、remove

  在remove方法中,主要完成了三件事:

  1. 获取哈希表。
  2. 计算索引,根据 key(即 ThreadLocal 对象)的哈希码,计算它在哈希表中的索引。
  3. 遍历表格中的链表查找匹配的条目。

  而expungeStaleEntry方法中,除了将value和entry的引用全部置空以外,还会继续向后扫描,将ThreadLocalMap中弱引用的key已经被回收的entry的value置空。(get和set方法中也有该实现)

        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) {
                		 //将key的引用置空
                    e.clear();
                    //一
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

			 //一
			 private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            //将指定下标的value的引用置空
            tab[staleSlot].value = null;
            //将指定下标的entry置空
            tab[staleSlot] = null;
            //table的长度减少
            size--;

            //这部分代码会继续扫描从 staleSlot 后的条目。如果遇到 ThreadLocal 对象已经被回收(k == null),则清除该条目。
            //否则,重新计算哈希位置并尝试将条目移动到新的位置。
            //这里使用了一个 while 循环来处理条目的重新定位,确保哈希表在移除过期条目后依然保持正确。
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

六、思考:为什么要将ThreadLocalMap的value设置为强引用?

  线程本地存储的一个核心需求是,数据必须与线程的生命周期绑定,直到该线程结束或者显式移除该值。如果线程本地存储的 value 被弱引用,就无法保证它在使用时的可用性,可能会导致意外的回收和不可预期的行为。如果 ThreadLocalMap 的 value 被设置为弱引用,那么 ThreadLocalMap 中的条目就可能会在垃圾回收时被回收,因为 value 被弱引用(即没有强引用指向它)。这就可能导致在需要访问该值时,数据已经被清理掉。
  最主要的点在于,ThreadLocalMap 的生命周期跟 Thread 一样长。


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

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

相关文章

批量解密,再也没有任何限制了

有的时候我们在网上下载了PDF文档。发现没有办法进行任何的操作&#xff0c;就连打印权限都没有。今天给大家介绍的这个软件可以一键帮你进行PDF解密&#xff0c;非常方便&#xff0c;完全免费。 PDF智能助手 批量解密PDF文件 这个软件不是很大&#xff0c;只有10MB&#xff…

《LLM大语言模型+RAG实战+Langchain+ChatGLM-4+Transformer》

文章目录 Langchain的定义Langchain的组成三个核心组件实现整个核心组成部分 为什么要使用LangchainLangchain的底层原理Langchain实战操作LangSmithLangChain调用LLM安装openAI库-国内镜像源代码运行结果小结 使用Langchain的提示模板部署Langchain程序安装langserve代码请求格…

车载软件 --- 大一新生入门汽车零部件嵌入式开发

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 简单&#xff0c;单纯&#xff0c;喜欢独处&#xff0c;独来独往&#xff0c;不易合同频过着接地气的生活…

有效运作神经网络

内容来自https://www.bilibili.com/video/BV1FT4y1E74V&#xff0c;仅为本人学习所用。 文章目录 训练集、验证集、测试集偏差、方差正则化正则化参数为什么正则化可以减少过拟合Dropout正则化Inverted Dropout其他的正则化方法数据增广Early stopping 归一化梯度消失与梯度爆…

【深度优先搜索篇】走迷宫的魔法:算法如何破解迷宫的神秘密码

当你在夜晚孤军奋战时&#xff0c;满天星光以为你而闪烁。 欢迎拜访&#xff1a;羑悻的小杀马特.-CSDN博客 本篇主题&#xff1a;轻轻松松拿捏洛谷走迷宫问题 制作日期&#xff1a;2024.12.31 隶属专栏&#xff1a;C/C题海汇总 首先我…

SQL进阶实战技巧:如何分析浏览到下单各步骤转化率及流失用户数?

目录 0 问题描述 1 数据准备 2 问题分析 3 问题拓展 3.1 跳出率计算 3.2 计算从浏览商品到支付订单的不同路径的用户数&#xff0c;并按照用户数降序排列。 往期精彩 0 问题描述 统计从浏览商品到最终下单的各个步骤的用户数和流失用户数,并计算转化率 用户表结构和…

Autosar-Os是怎么运行的?(内存保护)

写在前面&#xff1a; 入行一段时间了&#xff0c;基于个人理解整理一些东西&#xff0c;如有错误&#xff0c;欢迎各位大佬评论区指正&#xff01;&#xff01;&#xff01; 1.功能概述 以TC397芯片为例&#xff0c;英飞凌芯片集成了MPU模块&#xff0c; MPU模块采用了硬件机…

什么是Maxscript?为什么要学习Maxscript?

MAXScript是Autodesk 3ds Max的内置脚本语言,它是一种与3dsMax对话并使3dsMax执行某些操作的编程语言。它是一种脚本语言,这意味着您不需要编译代码即可运行。通过使用一系列基于文本的命令而不是使用UI操作,您可以完成许多使用UI操作无法完成的任务。 Maxscript是一种专有…

(一)QT的简介与环境配置WIN11

目录 一、QT的概述 二、QT的下载 三、简单编程 常用快捷键 一、QT的概述 简介 Qt&#xff08;发音&#xff1a;[kjuːt]&#xff0c;类似“cute”&#xff09;是一个跨平台的开发库&#xff0c;主要用于开发图形用户界面&#xff08;GUI&#xff09;应用程序&#xff0c;…

vim交换文件的作用

1.数据恢复&#xff1a;因为vim异常的退出&#xff0c;使用交换文件可以恢复之前的修改内容。 2.防止多人同时编辑&#xff1a;vim检测到交换文件的存在,会给出提示&#xff0c;以避免一个文件同时被多人编辑。 &#xff08;vim交换文件的工作原理&#xff1a;vim交换文件的工作…

SpringCloudGateWay和Sentinel结合做黑白名单来源控制

假设我们的分布式项目&#xff0c;admin是8087&#xff0c;gateway是8088&#xff0c;consumer是8086 我们一般的思路是我们的请求必须经过我们的网关8088然后网关转发到我们的分布式项目&#xff0c;那我要是没有处理我们绕过网关直接访问项目8087和8086不也是可以&#xff1…

将多目标贝叶斯优化与强化学习相结合用于TinyML

论文标题 Combining Multi-Objective Bayesian Optimization with Reinforcement Learning for TinyML 作者信息 Mark Deutel, Friedrich-Alexander-Universitt Erlangen-Nrnberg, Germany Georgios Kontes, Fraunhofer IIS, Fraunhofer Institute for Integrated Circuits …

Big Bird:适用于更长序列的Transformer模型

摘要 基于Transformer的模型&#xff0c;如BERT&#xff0c;已成为自然语言处理&#xff08;NLP&#xff09;中最成功的深度学习模型之一。然而&#xff0c;它们的一个核心限制是由于其全注意力机制&#xff0c;对序列长度的二次依赖&#xff08;主要是在内存方面&#xff09;…

26_DropDown使用方法

创建下拉框DropDown 其中样板Template 是展示的选项框 其中Caption 是选中某个选项之后 展示的内容&#xff08;Caption Text 说明文字/Caption Image 说明图示&#xff09; 修改其 说明文字Caption Text 创建一个说明图示Image 设置为居左 而Item是 展示的选项框所展示的文字与…

【redis进阶】redis 总结

目录 介绍一下什么是 Redis&#xff0c;有什么特点 Redis 支持哪些数据类型 Redis 数据类型底层的数据结构/编码方式是什么 ZSet 为什么使用跳表&#xff0c;而不是使用红黑树来实现 Redis 的常见应用场景有哪些 怎样测试 Redis 服务器的连通性 如何设置 key 的过期时间 Redis …

AI大模型开发原理篇-1:语言模型雏形之N-Gram模型

N-Gram模型概念 N-Gram模型是一种基于统计的语言模型&#xff0c;用于预测文本中某个词语的出现概率。它通过分析一个词语序列中前面N-1个词的出现频率来预测下一个词的出现。具体来说&#xff0c;N-Gram模型通过将文本切分为长度为N的词序列来进行建模。 注意&#xff1a;这…

Linux工具使用

1.gcc/g的使用 1.1程序翻译的过程 ①预处理&#xff1a;展开头文件&#xff0c;替换宏&#xff0c;调节编译&#xff0c;去注释。 ②编译&#xff1a;将代码变成汇编语言 ③汇编&#xff1a;将汇编代码变成二进制不可执行的目标文件。 ④链接&#xff1a;将多个我写的多个…

后端token校验流程

获取用户信息 前端中只有 await userStore.getInfo() 表示从后端获取数据 在页面中找到info对应的url地址&#xff0c;在IDEA中查找 这里是getInfo函数的声明&#xff0c;我们要找到这个函数的使用&#xff0c;所以点getInfo() Override public JSONObject getInfo() {JSO…

Python 梯度下降法(二):RMSProp Optimize

文章目录 Python 梯度下降法&#xff08;二&#xff09;&#xff1a;RMSProp Optimize一、数学原理1.1 介绍1.2 公式 二、代码实现2.1 函数代码2.2 总代码 三、代码优化3.1 存在问题3.2 收敛判断3.3 函数代码3.4 总代码 四、优缺点4.1 优点4.2 缺点 Python 梯度下降法&#xff…

excel如何查找一个表的数据在另外一个表是否存在

比如“Sheet1”有“张三”、“李四”“王五”三个人的数据&#xff0c;“Sheet2”只有“张三”、“李四”的数据。我们通过修改“Sheet1”的“民族”或者其他空的列&#xff0c;修改为“Sheet2”的某一列。这样修改后筛选这个修改的列为空的或者为出错的&#xff0c;就能找到两…