图解方式来通关ThreadLocal,同时但愿大家有必定的JVM 基础,这样食用起来会更香。程序员
相信大伙对 ThreadLocal 并不陌生,工做中经常使用,同时也是面试高频题,可是大部分人对 ThreadLocal 的理解可能只是「线程的本地变量,Map结构」,看完本文让大伙真正理解ThreadLocal,给大伙工做带来帮助,也让面试有更多的谈资。面试
在聊 ThreadLocal 前,先作前置知识铺垫,谈谈Java对象引用级别。数据库
为了使程序能更灵活地控制对象生命周期,从 JDK1.2 版本开始,JDK把对象的引用级别由高到低分为强引用、软引用、弱引用、虚引用四种级别。数组
强引用是咱们最多见的对象,它属于不可回收资源,垃圾回收器(后面简称G C)绝对不会回收它,即便是内存不足,J V M宁愿抛出 OutOfMemoryErrorM 异常,使程序终止,也不会来回收强引用对象。缓存
若是对象是软引用,那它的性质属于无关紧要,由于内存空间充足的状况下,G C不会回收它,可是内存空间紧张,G C发现它仅有软引用,就会回收该对象,因此软引用对象适合做为内存敏感的缓存对象。安全
只有对象仅被 SoftReference 引用,它才是软引用级别对象,由于对象能够在多处被引用,因此 SoftReference 引用的对象,它可能在其余处被强引用了。网络
弱引用对象相对软引用对象具备更短暂的生命周期,只要 G C 发现它仅有弱引用,无论内存空间是否充足,都会回收它,不过 G C 是一个优先级很低的线程,所以不必定会很快发现那些仅有弱引用的对象。框架
只有对象仅被 WeakReference 引用,它才是弱引用级别对象,由于对象能够在多处被引用,因此 WeakReference 引用的对象,它可能在其余处被强引用了。分布式
顾名思义,虚引用形同虚设,与其余几种引用不一样,虚引用不会决定对象的生命周期。工具
若是一个对象仅有虚引用,那它就和没有任何引用同样,任什么时候候均可能被 G C 回收。
读到这里会不会感受虚引用和弱引用没区别?它们的区别以下
简单说就是「没法经过虚引用来获取对象的真实地址」
Java中SoftReference、WeakReference、PhantomReference,能够理解为对象引用级别包装类,在项目中使用对应的包装类,赋予对象引用级别。
虚引用图中,出现了ReferenceQueue(引用队列),引用队列是配合对象引用级别包装类(SoftReference、WeakReference、PhantomReference)使用,当对象引用级别包装类所指向的对象,被垃圾回收后,该对象引用级别包装类被追加到引用队列,所以能够经过引用队列作 G C 相关统计或额外数据清理等操做。
ThreadLocal不少地方叫线程本地变量,也有些地方叫线程本地存储,其实意思差很少。ThreadLocal为变量在每一个线程中都建立了一个副本,每一个线程能够访问本身内部的副本变量。
Thread类声明了成员变量threadLocals,threadLocals才是真正的线程本地变量,所以每一个 Thread 都有本身的线程本地变量,因此线程本地变量拥有线程隔离特性,也就是天生的线程安全。
从上图能够看到 threadLocals 成员变量类是 ThreadLocal.ThreadLocalMap,便是 ThreadLocal 提供的内部类,所以 Thread 线程本地变量的建立、新增、获取、删除实现核心,必然是围绕 threadLocals,因此开发者也是围绕 threadLocals 实现功能,为了后续重复使用,还会对代码实现进行封装复用,而 ThreadLocal 就是线程本地变量工具类,由 J D K 提供,线程本地变量的功能都已经实现好了,开箱即用,造福广大开发人员。
ThreadLocal经常使用的方法
一个 Threa能够拥有多个 ThreadLocal键值对(存储在ThreadLocalMap结构),又由于 ThreadLocalMap 依赖当前Thread,Thread销毁时 ThreadLocalMap 也会随之销毁,因此 ThreadLocalMap 的生命周期与 Thread 绑定。
如今总结出「本地线程变量的做用域,属于当前线程整个范围,一个线程能够跨越多个方法使用本地线程变量」,当你但愿某些变量在某 Thread 的多个方法中共享 并保证线程安全,那就大胆的使用ThreadLocal(ps:必定要想清楚,是某个变量被Thread生命周期内多个方法共享,仍是多个Thread共享这个变量!)。
先来看看User类实现的线程本地变量代码
方法也很少,分别是initialValue、get、set、remove,接下来这些方法源码进行解析。
为了后面的源码解析体验更好,有必要介绍下ThreadLocalMap,顾名思义,它是 Map 结构,可是本文主要内容不是Map,因此上一图,快速过一下这块内容。
经过上图,相信大伙对 ThreadLocalMap 结构已经很是清晰,不知有没有细心的小伙伴发现 ThreadLocal 竟被弱引用持有?
为何ThreadLocal会被弱引用?这块疑惑后面会给大伙安排的明明白白,最后上一张 ThreadLocalMap 源码图。
步骤以下
步骤以下
步骤以下
步骤以下
源码十分简单,核心就三样ThreadLocal线程本地变量工具类(同时做为索引)、Entry基本元素(由弱引用包装类ThreadLocal与value组成),Entry数组容器,到这里流程很清晰了,ThreadLocal计算出数组索引,用 ThreadLocal 与 value 构建出 Entry 元素,最终放入 Entry 容器中,相信大伙都能写出来。
为何 Entry 中对 ThreadLocal 使用弱引用?反问一句,若是使用强引用,会发生什么事情?
上图的代码做用仅仅只是是为了让大伙去理解为何使用弱引用,通常开发中不会出现这样的代码(真出现了,这程序员怕是要拉去祭天)。
回到正题,咱们快速对代码进行解析,首先 ThreadContextTest 持有私有的静态变量 ThreadLocal,且 ThreadContextTest 禁止实例化,接着执行静态方法 run 触发静态块为 ThreadLocal 设置User变量 并消除 ThreadLocal 强引用,此时当前线程的本地变量拥有了Entry元素。
问题来了,要如何获取到 Entry 元素,按正常流程,ThreadLocal执行 get 方法,get会使用当前 ThreadLocal 计算出索引,最终获取到Entry元素,但是如今的问题如同下图。
咱们不知道 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,理由以下
先祝大伙新年快乐,万事如意!!!博主两周肝一篇,虽然周期有点长,可是质量有保证,码文不易,若是以为本文对您有帮助,欢迎分享给你的朋友,也给阿星点个「点赞+收藏」,这对阿星很是重要,谢谢您们,给各位小姐姐小哥哥们抱拳了,咱们下次见!
公众号 : 「程序猿阿星」 专一技术原理、源码,经过图解方式输出技术,这里将会分享操做系统、计算机网络、Java、分布式、数据库等精品原创文章,期待你的关注。