当前位置: 首页 > 后端技术 > Java

还有谁不能全方位、多角度的理解ThreadLocal??

时间:2023-04-01 20:34:52 Java

来源:blog.csdn.net/zzg1229059735/article/details/82715741这次介绍重要的工具ThreadLocal。说明内容如下。同时介绍了内存泄漏发生在哪些场景,如何重现内存泄漏,以及如何正确使用来避免内存泄漏。什么是线程本地?有什么用途?如何使用ThreadLocalThreadLocal的原理使用ThreadLocal有哪些陷阱和注意事项?有什么用途?首先引入Thread类中的属性threadLocals:/*ThreadLocalvaluespertainedtothisthread。该映射由ThreadLocal类维护*。*/ThreadLocal.ThreadLocalMapthreadLocals=null;我们发现Thread并没有提供成员变量threadLocals方法的设置和访问,那么我们如何操作每个线程的实例threadLocals参数呢?这时候我们的主角:ThreadLocal就登场了。于是就有了总结:ThreadLocal是线程Thread中属性threadLocals的管理者。也就是说,我们对ThreadLocal的get、set、remove操作的结果,都是针对当前线程Thread实例的threadLocals存储、检索、删除操作。类似于开发人员的任务,产品经理是没有控制权的,产品经理只能通过技术负责人给开发人员分配任务。再举个例子进一步说明他们之间的关系:每个人都有一张银行卡,每张卡都有一定的余额。大家必须通过银行的管理系统才能获得银行卡余额。每个人只能获取自己卡持有的余额信息,其他人无法访问。映射到我们要说的ThreadLocalcard类似于Threadcard余额属性,卡号属性等,类似于Treadlocal内部属性集合threadLocalscardManager类似于ThreadLocal管理类,那么ThreadLocal有哪些应用场景呢?事实上,我们一直在无意中使用ThreadLocal提供的便利。如果你对多数据源的切换不熟悉,那么spring提供的声明式事务就很熟悉了。我们在研发过程中一直在使用,而spring声明式事务的重要实现基础就是ThreadLocal,但是大家并没有深入研究过spring声明式事务的实现机制。后面会为大家介绍spring声明式事务的原理和实现机制。原来ThreadLocal这么强大,应用开发者却很少用到它。同时,一些开发者不敢尝试ThreadLocal内存泄漏等潜在问题。这恐怕是对ThreadLocal最大的误解。后面我们会仔细分析,只要使用方法正确,是没有问题的。如果ThreadLocal出了问题,springdeclarativetransaction不就是我们程序最大的潜在危险吗?2.如何使用ThreadLocal为了更直观的体验ThreadLocal的使用,我们假设如下场景,我们为每个线程生成一个ID。一旦设置,就不能在线程生命周期内更改。容器活动期间不能生成重复的ID。我们创建一个ThreadLocal管理类:测试过程如下:不断获取同一个线程,测试ID是否变化,测试完成后释放。在主程序中,我们开启多个线程来测试未连接的线程之间是否会产生影响。我们的结果是:结果:确实是不同的线程有不同的id,同一个线程有相同的id。3.ThreadLocal原理①ThreadLocal类结构及方法分析:从上图可以看出:ThreadLocal的get、set、remove三个方法和内部类ThreadLocalMap②ThreadLocal与Thread的关系:从这张图我们可以直观的看出Thread中的属性threadLocals,作为一个特殊的Map,它的key值为我们的ThreadLocal实例,value值为我们设置的值。③ThreadLocal操作过程:以get方法为例:getMap(t)返回当前线程的threadlocals,如下图,然后根据当前ThreadLocal实例对象获取ThreadLocalMap中的值作为钥匙。如果是第一次进来,调用setInitialValue()set的过程也类似:注意:t.threadLocals可以直接在ThreadLocal中使用,因为Thread和ThreadLocal在同一个包下,Thread也可以直接访问ThreadLocal。ThreadLocalMapthreadLocals=null;声明属性。4、使用ThreadLocal有哪些陷阱和注意事项?我经常在互联网上看到令人震惊的标题。ThreadLocal导致内存泄漏。这通常会让一些一开始不了解ThreadLocal的开发者不敢贸然使用。你用得越少,它就越陌生。这样就错过了一个更好的实施方案,所以才敢于引进新技术,踩坑不断进步。下面我们来看看为什么ThreadLocal会导致内存泄漏,以及哪些场景会导致内存泄漏?先回顾一下什么是内存泄漏,对应的内存溢出是什么①内存溢出:内存溢出,没有足够的内存供申请人使用。②Memoryleak:内存泄漏。程序申请内存后,无法释放申请的内存空间。内存泄漏的积累最终会导致内存溢出。很明显,TreadLocal的不规范使用导致内存没有释放。在红色框中,我们看到了一个特殊的类WeakReference。同样的类也很少被应用程序开发者使用。在这里简单介绍一下。既然WeakReference会在下次gc的时候被回收,为什么我们的程序没有问题呢?①所以我们测试一下弱引用的回收机制:这种强引用是不会被回收的。这里不会收集强引用。以上演示了弱引用的回收。再来看ThreadLocal中弱引用的回收。②ThreadLocal的弱引用回收情况如上图所示。我们没有将ThreadLocal对象作为键的外部强引用。下次gc肯定会产生key值为null的数据。如果线程不及时结束,它不可避免地会出现。强引用链Threadref–>Thread–>ThreadLocalMap–>Entry,所以这会造成内存泄漏。下面我们模拟复制ThreadLocal造成的内存泄漏:1.为了让效果更明显,我们将threadlocals的存储值设置为10000个字符串的列表:classThreadLocalMemory{//Threadlocalvariablecontainingeachthread'sIDpublicThreadLocal>threadId=newThreadLocal>(){@OverrideprotectedListinitialValue(){Listlist=newArrayList();对于(inti=0;i<10000;i++){list.add(String.valueOf(i));}返回列表;}};//返回当前线程的唯一ID,必要时分配它publicListget(){returnthreadId.get();}//删除当前IDpublicvoidremove(){threadId.remove();}}测试代码如下:publicstaticvoidmain(String[]args)throwsInterruptedException{//为了重现key被回收的场景,我们使用临时变量ThreadLocalMemorymemeory=newThreadLocalMemory();//称呼incrementSameThreadId(内存);System.out.println("GC前:key:"+memeory.threadId);System.out.println("GC之前:值大小:"+reflectThreadLocals(Thread.currentThread()));//设置为null,调用gc不一定会触发垃圾回收,但是可以通过java提供的一些工具手动触发gc回收memeory.threadId=null;系统.gc();System.out.println("GC后:key:"+memeory.threadId);System.out.println("GC后:值大小:"+reflectThreadLocals(Thread.currentThread()));//模拟线程一直在运行while(true){}}这个时候我们怎么知道内存有内存泄漏呢?我们可以使用jdk提供的一些命令来dump当前堆内存。命令如下:jmap-dump:live,format=b,file=heap.bin然后我们使用MAT可视化分析工具查看内存,分析对象实例生存状态:首先打开我们的工具提示我们的内存泄漏分析:这里可以确定ThreadLocalMap实例的Entry.value没有被回收。最后,我们要判断Entry.key是否还在?打开DominatorTree,搜索我们的ThreadLocalMemory,发现没有存活的实例。上面我们复现了ThreadLocal使用不当导致的内存泄漏。演示在这里。所以我们总结一下使用ThreadLocal时出现内存泄漏的前提条件:①ThreadLocal的引用被设置为null,并且其背后没有set、get、remove操作。②线程一直在运行,没有停止。(线程池)③垃圾回收被触发。(MinorGCorFullGC)我们看到ThreadLocal内存泄漏的条件还是很苛刻的,所以我们只需要破坏其中一个条件就可以避免内存泄漏,但是为了更好的避免这种情况的发生,我们使用了ThreadLocal.下面两个小原则:①ThreadLocal声明为privatestaticfinal。private和final尽量不要让别人修改和更改引用。static表示为类属性,只会在程序结束时被回收。②使用ThreadLocal后一定要调用remove方法。最简单有效的方法是在使用后将其移除。多于。近期热点文章推荐:1.1000+Java面试题及答案(2022最新版)2.厉害了!Java协程来了。..3.SpringBoot2.x教程,太全面了!4、SpringBoot2.6正式发布,一大波新特性。.5.《Java开发手册(嵩山版)》最新发布,赶快下载吧!感觉不错,别忘了点赞+转发!