本篇内容主要讲解“ThreadLocal的结构是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“ThreadLocal的结构是什么”吧!
在聊 ThreadLocal 前,先做前置知识铺垫,谈谈Java对象引用级别。
为了使程序能更灵活地控制对象生命周期,从 JDK1.2 版本开始,JDK把对象的引用级别由高到低分为强引用、软引用、弱引用、虚引用四种级别。
强引用是我们最常见的对象,它属于不可回收资源,垃圾回收器(后面简称G C)绝对不会回收它,即使是内存不足,J V M宁愿抛出 OutOfMemoryErrorM 异常,使程序终止,也不会来回收强引用对象。
如果对象是软引用,那它的性质属于可有可无,因为内存空间充足的情况下,G C不会回收它,但是内存空间紧张,G C发现它仅有软引用,就会回收该对象,所以软引用对象适合作为内存敏感的缓存对象。
只有对象仅被 WeakReference 引用,它才是弱引用级别对象,因为对象可以在多处被引用,所以 WeakReference 引用的对象,它可能在其他处被强引用了。
顾名思义,虚引用形同虚设,与其他几种引用不同,虚引用不会决定对象的生命周期。
如果一个对象仅有虚引用,那它就和没有任何引用一样,任何时候都可能被 G C 回收。
ThreadLocal很多地方叫线程本地变量,也有些地方叫线程本地存储,其实意思差不多。ThreadLocal为变量在每个线程中都创建了一个副本,每个线程可以访问自己内部的副本变量。
Thread类声明了成员变量threadLocals,threadLocals才是真正的线程本地变量,因此每个 Thread 都有自己的线程本地变量,所以线程本地变量拥有线程隔离特性,也就是天生的线程安全。
从上图可以看到 threadLocals 成员变量类是 ThreadLocal.ThreadLocalMap,即是 ThreadLocal 提供的内部类,因此 Thread 线程本地变量的创建、新增、获取、删除实现核心,必然是围绕 threadLocals,所以开发者也是围绕 threadLocals 实现功能,为了后续重复使用,还会对代码实现进行封装复用,而 ThreadLocal 就是线程本地变量工具类,由 J D K 提供,线程本地变量的功能都已经实现好了,开箱即用,造福广大开发人员。
ThreadLocal常用的方法
set:为当前线程设置变量,当前ThreadLocal作为索引
get:获取当前线程变量,当前ThreadLocal作为索引
initialValue(钩子方法需要子类实现):赖加载形式初始化线程本地变量,执行get时,发现线程本地变量为null,就会执行initialValue的内容
remove:清空当前线程的ThreadLocal索引与映射的元素
现在总结出「本地线程变量的作用域,属于当前线程整个范围,一个线程可以跨越多个方法使用本地线程变量」,当你希望某些变量在某 Thread 的多个方法中共享 并保证线程安全,那就大胆的使用ThreadLocal(ps:一定要想清楚,是某个变量被Thread生命周期内多个方法共享,还是多个Thread共享这个变量!)。
先来看看User类实现的线程本地变量代码
通过上图,相信大伙对 ThreadLocalMap 结构已经非常清晰,不知有没有细心的小伙伴发现 ThreadLocal 竟被弱引用持有?
为什么ThreadLocal会被弱引用?这块疑惑后面会给大伙安排的明明白白,最后上一张 ThreadLocalMap 源码图。
步骤如下
获取当前线程
获取当前线程的本地变量
线程本地变量没有被创建,执行setInitialValue方法进行初始化,并返回value值
线程本地变量存在,ThreadLocal计算成索引从 本地线程变量 获取Entry,如果Entry为null,执行setInitialValue方法进行初始化,并返回value值,否则通过Entry获取value返回
步骤如下
获取当前线程
获取线程本地变量
本地变量不为空,当前ThreadLocal为索引设置映射的value,否则创建线程本地变量再做后续的设置操作
为什么 Entry 中对 ThreadLocal 使用弱引用?反问一句,如果使用强引用,会发生什么事情?
我们不知道 key 是什么,如何去获取映射的value,同样的道理,都没有入口去获取到ThreadContextTest.ThreadLoca,自然没办法获取映射的Entry元素。
设计中采用Map结构存储数据,却不能通过key去获取value,这设计明显不合理,又因key、value值是强引用,导致 G C 无法回收,造成内存溢出。
所以针对这种不合理的设计场景 J D K 做了优化,对 Entry 中的 ThreadLocal 使用弱引用,当 G C 发现它仅有弱引用的时候,会进行回收。
还没结束,上面留了个小尾巴,大伙都知道 Entry 中对 ThreadLocal 使用弱引用,但value是强引用,如果出现上面提到的不合理场景,value值无法清理,最终内存溢出。
其实value作为强引用设计属于合理,如果用软或弱引用,就出大问题了,程序跑着跑着突然get到了一个null,估计都得骂娘了,所以为解决内存溢出问题 J D K提供remove方法,使开发人员可以选择手动清理整个Entry元素,防止内存溢出。
还记的之前说过吗?线程本地变量的生命周期与线程绑定,一般线程的生命周期比较短,线程结束时,线程本地变量自然就销毁了,软引用与 remove 会不会有点多余了?
业务瞬息万变,大部分情况来说线程的生命周期比较短,但也业务场景会导致线程的生命周期较长,甚至可能线程无限循环执行,这些是你没办法预料到的,数量一旦上来很容易内存溢出,所以个人建议使用完之后及时清理ThreadLocal,理由如下
生命周期较长的线程场景
无限循环线程的场景
线程池场景(因为线程池可以复用线程,而且公司使用的框架可能会定制化线程池,你不能保证他会在线程池内帮你remove)
到此,相信大家对“ThreadLocal的结构是什么”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。