Java ThreadLocal 实现原理 与 如何使用弱引用解决内存泄漏问题

目录

    • 一、ThreadLocal 有什么用
    • 二、ThreadLocal 使用示例
    • 三、ThreadLocal 实现原理
    • 四、ThreadLocal 如何是使用弱引用解决内存泄漏问题
      • 4.1、强引用内存泄漏分析
      • 4.1、弱引用解决内存泄漏问题

一、ThreadLocal 有什么用

      ThreadLocal 诞生于 JDK 1.2,用于解决多线程间的数据隔离问题。也就是说 ThreadLocal 会为每一个线程创建一个单独的变量副本,在 Servlet 中就会将Request 和 Response对象存入ThreadLocal,我们在当前线程任意方法中都能通过RequestContextHolder 获取到当前请求的Request 和 Response对象 。
HttpServletRequest request =((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();

二、ThreadLocal 使用示例

public static ThreadLocal<String> tlName = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
    tlName.set("张三");
    new Thread(()->{
        tlName.set("李四");
        System.out.println(Thread.currentThread().getName()+"-" + tlName.get()); // Thread-0-李四
    }).start();
    Thread.sleep(10);
    System.out.println(Thread.currentThread().getName()+"-"+tlName.get()); // main-张三
}

在这里插入图片描述
这里可以看到同一个ThreadLocal对象tlName 在不同线程中可以独立设置自己的值,线程之间不会互相影响,下面会分析实现原理。

三、ThreadLocal 实现原理

分析ThreadLocal实现原理可以从它的set方法入手,set方法的源码如下:

// ThreadLocal 的set方法传入一个value
public void set(T value) {
    Thread t = Thread.currentThread(); // 获取当前线程对象
    // 调用ThreadLocal的getMap方法传入当前线程对象
    // 通过当前线程对象获取到当前线程的ThreadLocalMap对象,每个线程对象中都存储了自己的ThreadLocalMap对象
    ThreadLocal.ThreadLocalMap map = getMap(t); 
    // 这里会判断当前线程对象是否已经创建ThreadLocalMap,如果没有创建会先执行创建并且set
    // 如果已经创建则将当前的ThreadLocal对象为key,传入的value为值存储到当前线程的ThreadLocalMap中。
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
// 这个方法就是通过线程对象点属性获取ThreadLocalMap
ThreadLocal.ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

可以看到,ThreadLocal为每个线程创建了一个ThreadLocalMap,在set的时候key就是当前的ThreadLocal对象,value就是set里的值,以上测试代码执行时逻辑内存空间如下图:
在这里插入图片描述

四、ThreadLocal 如何是使用弱引用解决内存泄漏问题

4.1、强引用内存泄漏分析

假设ThreadLocal没有使用弱引用而是使用的强引用,则会出现内存泄漏问题,这里对原理进行分析:

现在我们只考虑tlName这个对象,它通过new ThreadLocal<>()开辟了一个内存空间,当某线程进行set时,又在内存中开辟了一个空间存放ThreadLocalMap,线程对象的threadLocals对象指向这个ThreadLocalMapThreadLocalMapkeytlName这个对象,valueset的值,如下:

在这里插入图片描述

现在如果我们在线程中执行tlName = null,从逻辑上讲这个强引用就断开了,通过new ThreadLocal<>()开辟的内存空间就没用了,应该属于垃圾被GC回收,但问题是线程对象并没释放,其属性threadLocals还指向该内存空间,根据垃圾回收可达性算法,这两部分内存空间是不能被清除掉的,在使用线程池的业务中很容易出现这种问题,因为线程对象会出现复用的情况。
在这里插入图片描述

当然我们也可以手动将ThreadLocalMap中对tlName的引用删除,手动调用remove清除即可,但是在实际工作做并不一定记得手动清除,或者因为一些异常导致没有清除,这个时候还是会存在内存泄漏,ThreadLocal会使用弱引用来解决这个问题。

4.1、弱引用解决内存泄漏问题

      要想探究这个问题要先分析一下每个线程对象的threadLocals属性,这个属性是ThreadLocal.ThreadLocalMap 类型的,在ThreadLocal 第一次调用set方法时创建,通过上述的ThreadLocal实现原理可以知道,ThreadLocalMap就是每个线程真实用来存储ThreadLocalvalue的,其中key就是ThreadLocal对象,下面来分析一下ThreadLocalMap结构。

public class ThreadLocal<T> {
	// ... ...
    static class ThreadLocalMap {
    	// ThreadLocalMap 的Entry 对象实现了弱引用
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            // Entry 的构造方法 传入ThreadLocal 和value
            Entry(ThreadLocal<?> k, Object v) {
            	// 调用父类弱引用WeakReference的构造方法将ThreadLocal传入托管给弱引用管理
                super(k);
                value = v;
            }
        }
        // 多个ThreadLocal与value会存储在这个Entry数组中
        private ThreadLocal.ThreadLocalMap.Entry[] table;
		// 将ThreadLocal与value存储到Entry数组table中,并且会进行清理槽位判断
		private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (ThreadLocalMap.Entry e = tab[i];
                // ... ...
            }
            tab[i] = new Entry(key, value);
            int sz = ++size;
            // 当table中有Entry对象中的key为null时需要进行清理
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
        // 清理槽位
        private void rehash() {
            expungeStaleEntries();
            if (size >= threshold - threshold / 4)
                resize();
        }
        // ... ...
    }
}

通过上述代码可以看到其中Entry就是一个弱引用,会通过弱引用WeakReference的构造方法将ThreadLocal传入托管给弱引用管理,也就谁说ThreadLocal中的key是通过弱引用指向new ThreadLocal<>()开辟的内存空间,所以当tlName = null时,这段内存空间由于只有弱引用指向它,经过一次GC直接就被清除了,key自动变为null,达到了预期的效果。
在这里插入图片描述

通过上图可以看到new ThreadLocal<>()开辟的内存空间被回收了,ThreadLocalMapkey也变为null,但这个Entry对象还在table数组中,value张三也没有被回收,如果张三是个大对象,没用了又占据着内存空间,那么ThreadLocal还是存在内存泄漏问题,不过上述代码中有一个调用rehash清理槽位的逻辑,在每次set时都会判断是否有keynull,如果有的话会将value置为null

要想彻底解决ThreadLocal内存泄漏问题需要手动调用ThreadLocal提供remove方法,或者set(null)也行,其实我们平时写代码感觉很少主动去写tlName = null这样的操作,但是如果tlName 声明周期只在某个方法里,方法出栈,线程还在的情况下,tlName 就不再属于GC Roots引用了,和tlName = null效果是一样的,可能不经意就造成内存泄漏。

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

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

相关文章

基于ssm学院党员管理系统论文

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对鄂尔多斯应用技术学院党员信息管理混乱&#xff0c;出错率高&#x…

javaWebssh图书系统myeclipse开发mysql数据库MVC模式java编程计算机网页设计

系统前景 图书有很多&#xff0c;老的图书书的管理靠纸介质&#xff0c;浪费人力和物力&#xff0c;给图书管理者带来极大的资源浪费。随着计算机信息化的普及&#xff0c;对图书的管理带来本质的改变&#xff0c;图书的销售情况以及&#xff0c;图书管理&#xff0c;以及年终对…

振弦采集仪助力岩土工程质量控制

振弦采集仪助力岩土工程质量控制 随着工程建设规模越来越大&#xff0c;建筑结构的安全性和稳定性越来越成为人们所关注的焦点。岩土工程在工程建设中占据着非常重要的地位&#xff0c;岩土工程质量控制更是至关重要。而振弦采集仪作为一种先进的检测设备&#xff0c;正得到越…

Linux命令之ps

Linux命令之ps ps命令的基本用法**常用的ps命令示例** ps命令的基本用法 ps: 显示当前终端会话中属于当前用户的进程列表。 ps -ef: 显示系统中所有进程的列表&#xff0c;包括其他用户的进程。 ps -aux: 显示详细的进程信息&#xff0c;包括CPU和内存使用情况等。 -e选项&…

浅谈如何写开发信和报价?外贸邮件怎么写?

外贸开发信要写报价进去吗&#xff1f;写开发信能加产品价格吗&#xff1f; 开发信和报价是连接您和潜在客户之间的纽带&#xff0c;它们有助于传达您的价值主张、产品或服务的优势以及价格细节。蜂邮EDM将探讨如何撰写令人印象深刻的开发信和报价&#xff0c;以吸引更多的潜在…

AIGC:使用变分自编码器VAE实现MINIST手写数字生成

1 变分自编码器介绍 变分自编码器&#xff08;Variational Autoencoders&#xff0c;VAE&#xff09;是一种生成模型&#xff0c;用于学习数据的分布并生成与输入数据相似的新样本。它是一种自编码器&#xff08;Autoencoder&#xff09;的扩展&#xff0c;自编码器是一种用于…

严蔚敏数据结构p17(2.19)——p18(2.24) (c语言代码实现)

目录 2.19已知线性表中的元素以值递增有序排列,并以单链表作存储结构。试写一高效的算法,删除表中所有值大于 mink 且小于 maxk 的元素(若表中存在这样的元素&#xff09;同时释放被删结点空间,并分析你的算法的时间复杂度(注意:mink 和 maxk 是给定的个参变量,它们的值可以和表…

【JavaEE】生产者消费者模式

作者主页&#xff1a;paper jie_博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文于《JavaEE》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精力)打造&…

从零开始学习 JS APL(一):完整指南和实例解析

本章内容主要是按一下来&#xff1a; 操作DOM BOM 比如 控制网页元 素交互等各种网页 交互效果 以下是我总结笔记&#xff08;仅供参考&#xff09; webAPL 获取DOM对象 变量声明有三个 var let 和 const 我们应该用那个呢&#xff1f; 首先var 先排除&#xff0c;老派写法…

UDP协议实现群聊

代码&#xff1a; import java.awt.*; import java.awt.event.*; import javax.swing.*; import java.net.*; import java.io.IOException; import java.lang.String; public class liaotian extends JFrame{ private static final int DEFAULT_PORT8899; private J…

机器的深度强化学习算法可以被诱导

设计一个好的奖励函数是机器深度强化学习算法的关键之一。奖励函数用于给予智能体&#xff08;机器&#xff09;在环境中采取不同行动时的反馈信号&#xff0c;以指导其学习过程。一个好的奖励函数应该能够引导智能体朝着期望的行为方向学习&#xff0c;并尽量避免潜在的问题&a…

使用ASIRequest库进行Objective-C网络爬虫示例

在Objective-C中&#xff0c;ASIHTTPRequest是一个非常受欢迎的库&#xff0c;用于处理HTTP请求。它可用于下载网页内容&#xff0c;处理API请求&#xff0c;甚至进行复杂的网络交互。下面是一个简单的示例&#xff0c;展示了如何使用ASIHTTPRequest库来爬取网页代码。 首先&a…

【ROS2指南-9】Bag的record和play操作

目标&#xff1a;记录在某个话题上发布的数据&#xff0c;以便您可以随时回放和检查它。 教程级别&#xff1a;初学者 时间&#xff1a; 10分钟 内容 背景 先决条件 任务 1 设置 2 选择一个主题 3 ros2包记录 4 ros2 包信息 5 ros2包玩 概括 下一步 相关内容 背景 …

Java 简易版 TCP UDP聊天

客户端 import java.io.*; import java.net.Socket; import java.util.Date; import javax.swing.*;public class MyClient {private JFrame jf;private JButton jBsend;private JTextArea jTAcontent;private JTextField jText;private JLabel JLcontent;private Date data;pr…

自定义类加载器

通过继承ClassLoader类&#xff0c;重写findClass方法&#xff0c;实现自定义类加载器。 一、自定义类加载器 package com.molange.JavaSE.myclassloader;import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; impor…

MATLAB - 绘制立体图(平面+水深)

目录 代码结果 代码 % 在 X-Y 平面上绘图 % 正常绘制平面图 [X,Y,Z] peaks; contour(X,Y,Z,20); hold on% ****重点******************************************** % 改为三维视图&#xff0c;具体可以help % view(3); %此时的平面图对应z0 &#xff1b;默认az-37.5&#x…

大模型在企业知识库场景的落地思考

一、引言 在这个信息爆炸的时代&#xff0c;企业的知识库已不再是简单的数据堆砌&#xff0c;而是需要智能化、高效率的知识管理和利用。大模型作为AI领域的一个重要突破&#xff0c;正逐步成为企业知识库管理的强大助力。通过前面一段时间对于大模型在企业落地的深入调研和实…

Linux---逻辑卷管理

本章主要介绍逻辑卷的管理。 了解什么是逻辑卷创建和删除逻辑卷扩展逻辑卷缩小逻辑卷逻辑卷快照的使用 前面介绍了分区的使用&#xff0c;如果某个分区空间不够&#xff0c;想增加空间是非常困难的。所以&#xff0c;建议尽可能使用逻辑卷而非普通的分区&#xff0c;因为逻辑卷…

【C语言】数据在内存中的存储

目录 练笔 整型数据的存储&#xff1a; char 型数据——最简单的整型 整型提升&#xff1a; 推广到其他整形&#xff1a; 大小端&#xff1a; 浮点型数据的存储&#xff1a; 存储格式&#xff1a; 本篇详细介绍 整型数据&#xff0c;浮点型数据 在计算机中是如何储存的。…

Redis和MySQL双写一致性实用解析

1、背景 先阐明一下Mysql和Redis的关系&#xff1a;Mysql是数据库&#xff0c;用来持久化数据&#xff0c;一定程度上保证数据的可靠性&#xff1b;Redis是用来当缓存&#xff0c;用来提升数据访问的性能。 关于如何保证Mysql和Redis中的数据一致&#xff08;即缓存一致性问题…