热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

全方位,多角度理解ThreadLocal

欢迎关注方志朋的博客,回复”666“获面试宝典来源:https:blog.csdn.netzzg1229059735articledetails8271

欢迎关注方志朋的博客,回复”666“获面试宝典

来源:https://blog.csdn.net/zzg1229059735/article/details/82715741

本次给大家介绍重要的工具ThreadLocal。讲解内容如下,同时介绍什么场景下发生内存泄漏,如何复现内存泄漏,如何正确使用它来避免内存泄漏。

ThreadLocal是什么?有哪些用途?

ThreadLocal如何使用

ThreadLocal原理

ThreadLocal使用有哪些坑及注意事项

1.ThreadLocal是什么?有哪些用途?

首先介绍Thread类中属性threadLocals:

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

我们发现Thread并没有提供成员变量threadLocals的设置与访问的方法,那么每个线程的实例threadLocals参数我们如何操作呢?这时我们的主角:ThreadLocal就登场了。所以有那么一句总结:ThreadLocal是线程Thread中属性threadLocals的管理者。也就是说我们对于ThreadLocal的get, set,remove的操作结果都是针对当前线程Thread实例的threadLocals存,取,删除操作。类似于一个开发者的任务,产品经理左右不了,产品经理只能通过技术leader来给开发者分配任务。下面再举个栗子,进一步说明他们之间的关系:

5365b7c72b986454fbe11fcaea6c616d.png

1.每个人都一张银行卡 2.每个人每张卡都有一定的余额。3.每个人获取银行卡余额都必须通过该银行的管理系统。4.每个人都只能获取自己卡持有的余额信息,他人的不可访问。

6ef6927154e42ed223c5d2f679de5aae.png

映射到我们要说的ThreadLocal 1.card类似于Thread 2.card余额属性,卡号属性等类似于Treadlocal内部属性集合threadLocals 3.cardManager类似于ThreadLocal管理类

那ThreadLocal有哪些应用场景呢?其实我们无意间已经时时刻刻在使用ThreadLocal提供的便利,如果说多数据源的切换你比较陌生,那么spring提供的声明式事务就再熟悉不过了,我们在研发过程中无时无刻不在使用,而spring声明式事务的重要实现基础就是ThreadLocal,只不过大家没有去深入研究spring声明式事务的实现机制。后面有机会我会给大家介绍spring声明式事务的原理及实现机制。原来ThreadLocal这么强大,但应用开发者使用较少,同时有些研发人员对于ThreadLocal内存泄漏,等潜在问题,不敢试用,恐怕这是对于ThreadLocal最大的误解,后面我们将会仔细分析,只要按照正确使用方式,就没什么问题。如果ThreadLocal存在问题,岂不是spring声明式事务是我们程序最大的潜在危险吗?

2.ThreadLocal如何使用

为了更直观的体会ThreadLocal的使用我们假设如下场景

1.我们给每个线程生成一个ID。

2.一旦设置,线程生命周期内不可变化。

3.容器活动期间不可以生成重复的ID

我们创建一个ThreadLocal管理类:

9f5a82c84e140bb35460199b52419d6c.png

测试程序如下:我们同一个线程不断get,测试id是否变化,同时测试完成后我们就将其释放掉。

e16a658b44596106edd738e26f64c2bc.png

在主程序中我们开启多个线程测试不通线程之间是否会影响

b36dabfad1d6057acb9a01b92a380697.png

不出意外我们的结果为:

0aa20768b7bd735a8d5f09d295a1fde2.png

结果:确实是不同线程间id不同,相同线程id相同。

3.ThreadLocal原理

①ThreadLocal类结构及方法解析:

989c8c509a1a5cae2c09a5760c58071d.png

上图可知:ThreadLocal三个方法get, set , remove以及内部类ThreadLocalMap

②ThreadLocal及Thread之间的关系:

c59745d3cfcb5e8eebd778db9768a2b8.png

从这张图我们可以直观的看到Thread中属性threadLocals,作为一个特殊的Map,它的key值就是我们ThreadLocal实例,而value值这是我们设置的值。

③ThreadLocal的操作过程:

我们以get方法为例:

392c816b15092ddc7f92832dffdca7f8.png

其中getMap(t)返回的就上当前线程的threadlocals,如下图,然后根据当前ThreadLocal实例对象作为key获取ThreadLocalMap中的value,如果首次进来这调用setInitialValue()

810194a8afa3a6755043667fd975f057.png9234d49da95a9ee69b63e379a4a09914.png

set的过程也类似:

c018a20465bc5ef69dd2b8368fcc64a2.png

注意:ThreadLocal中可以直接t.threadLocals是因为Thread与ThreadLocal在同一个包下,同样Thread可以直接访问ThreadLocal.ThreadLocalMap threadLocals = null;来进行声明属性。

4.ThreadLocal使用有哪些坑及注意事项

我经常在网上看到骇人听闻的标题,ThreadLocal导致内存泄漏,这通常让一些刚开始对ThreadLocal理解不透彻的开发者,不敢贸然使用。越不用,越陌生。这样就让我们错失了更好的实现方案,所以敢于引入新技术,敢于踩坑,才能不断进步。

我们来看下为什么说ThreadLocal会引起内存泄漏,什么场景下会导致内存泄漏?

先回顾下什么叫内存泄漏,对应的什么叫内存溢出

①Memory overflow:内存溢出,没有足够的内存提供申请者使用。

②Memory leak:内存泄漏,程序申请内存后,无法释放已申请的内存空间,内存泄漏的堆积终将导致内存溢出。显然是TreadLocal在不规范使用的情况下导致了内存没有释放。

77bf370151a34f4f08f08dfc7145d2ee.png

红框里我们看到了一个特殊的类WeakReference,同样这个类,应用开发者也同样很少使用,这里简单介绍下吧

![](http://img.javafeichao.com//截屏2021-09-16 上午11.12.52.png)

既然WeakReference在下一次gc即将被回收,那么我们的程序为什么没有出问题呢?

①所以我们测试下弱引用的回收机制:

eee605eda26f733fd0806eaa469bae19.png

这一种存在强引用不会被回收。

这里没有强引用将会被回收。

bf39b6b2b41c92556650ecb6c86ce09f.png

上面演示了弱引用的回收情况,下面我们看下ThreadLocal的弱引用回收情况。

②ThreadLocal的弱引用回收情况

2fbf0a704f7577e3e1f058170f437627.png

如上图所示,我们在作为key的ThreadLocal对象没有外部强引用,下一次gc必将产生key值为null的数据,若线程没有及时结束必然出现,一条强引用链 Threadref–>Thread–>ThreadLocalMap–>Entry,所以这将导致内存泄漏。

下面我们模拟复现ThreadLocal导致内存泄漏:1.为了效果更佳明显我们将我们的treadlocals的存储值value设置为1万字符串的列表:

class ThreadLocalMemory {// Thread local variable containing each thread&#39;s IDpublic ThreadLocal threadId &#61; new ThreadLocal() {&#64;Overrideprotected List initialValue() {List list &#61; new ArrayList();for (int i &#61; 0; i < 10000; i&#43;&#43;) {list.add(String.valueOf(i));}return list;}};// Returns the current thread&#39;s unique ID, assigning it if necessarypublic List get() {return threadId.get();}// remove currentidpublic void remove() {threadId.remove();}
}

测试代码如下&#xff1a;

public static void main(String[] args)throws InterruptedException {//  为了复现key被回收的场景&#xff0c;我们使用临时变量ThreadLocalMemory memeory &#61; new ThreadLocalMemory();// 调用incrementSameThreadId(memeory);System.out.println("GC前&#xff1a;key:" &#43; memeory.threadId);System.out.println("GC前&#xff1a;value-size:" &#43; refelectThreadLocals(Thread.currentThread()));// 设置为null&#xff0c;调用gc并不一定触发垃圾回收&#xff0c;但是可以通过java提供的一些工具进行手工触发gc回收。memeory.threadId &#61; null;System.gc();System.out.println("GC后&#xff1a;key:" &#43; memeory.threadId);System.out.println("GC后&#xff1a;value-size:" &#43; refelectThreadLocals(Thread.currentThread()));// 模拟线程一直运行while (true) {}}

此时我们如何知道内存中存在memory leak呢&#xff1f;

我们可以借助jdk提供的一些命令dump当前堆内存&#xff0c;命令如下&#xff1a;

jmap -dump:live,format&#61;b,file&#61;heap.bin

然后我们借助MAT可视化分析工具&#xff0c;来查看对内存&#xff0c;分析对象实例的存活状态&#xff1a;

43adb1d6efd56fa466afd291b7a3a721.png053cfa1f323a18f9ea872819334b9cac.png

首先打开我们工具提示我们的内存泄漏分析&#xff1a;

0ca4b6be456ecf54418c26c3d10b1c13.png

这里我们可以确定的是ThreadLocalMap实例的Entry.value是没有被回收的。

最后我们要确定Entry.key是否还在&#xff1f;打开Dominator Tree&#xff0c;搜索我们的ThreadLocalMemory&#xff0c;发现并没有存活的实例。

829425bed46753840fe94a0d1e19ef2f.png596bb0df61a0034be27196895f922b3b.png

以上我们复现了ThreadLocal不正当使用&#xff0c;引起的内存泄漏。demo在这里。

所以我们总结了使用ThreadLocal时会发生内存泄漏的前提条件&#xff1a;

①ThreadLocal引用被设置为null&#xff0c;且后面没有set&#xff0c;get,remove操作。

②线程一直运行&#xff0c;不停止。&#xff08;线程池&#xff09;

③触发了垃圾回收。&#xff08;Minor GC或Full GC&#xff09;

我们看到ThreadLocal出现内存泄漏条件还是很苛刻的&#xff0c;所以我们只要破坏其中一个条件就可以避免内存泄漏&#xff0c;单但为了更好的避免这种情况的发生我们使用ThreadLocal时遵守以下两个小原则:

①ThreadLocal申明为private static final。Private与final 尽可能不让他人修改变更引用&#xff0c; Static 表示为类属性&#xff0c;只有在程序结束才会被回收。

②ThreadLocal使用后务必调用remove方法。最简单有效的方法是使用后将其移除。

热门内容&#xff1a;

  • MySQL 8.0 可以操作 JSON 了&#xff0c;牛逼。。。

  • 如何写出让同事无法维护的代码&#xff1f;

  • 抖音的服务器究竟有多大&#xff1f;

  • 重磅消息&#xff1a;Spring 6 和Spring Boot 3

  • 有个程序员老公有多爽&#xff1f;&#xff1f;&#xff1f;

f685d3d4c6125b67f75b0da37dece7b4.png

最近面试BAT&#xff0c;整理一份面试资料《Java面试BAT通关手册》&#xff0c;覆盖了Java核心技术、JVM、Java并发、SSM、微服务、数据库、数据结构等等。
获取方式&#xff1a;点“在看”&#xff0c;关注公众号并回复 666 领取&#xff0c;更多内容陆续奉上。

明天见(&#xff61;&#xff65;ω&#xff65;&#xff61;)&#xff89;♡



推荐阅读
  • 开发笔记:Java是如何读取和写入浏览器Cookies的
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Java是如何读取和写入浏览器Cookies的相关的知识,希望对你有一定的参考价值。首先我 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文介绍了如何在给定的有序字符序列中插入新字符,并保持序列的有序性。通过示例代码演示了插入过程,以及插入后的字符序列。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 从零学Java(10)之方法详解,喷打野你真的没我6!
    本文介绍了从零学Java系列中的第10篇文章,详解了Java中的方法。同时讨论了打野过程中喷打野的影响,以及金色打野刀对经济的增加和线上队友经济的影响。指出喷打野会导致线上经济的消减和影响队伍的团结。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
author-avatar
黄晓敏3023
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有