温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

ThreadLocal的结构是什么

发布时间:2021-10-12 11:02:45 来源:亿速云 阅读:189 作者:iii 栏目:编程语言

本篇内容主要讲解“ThreadLocal的结构是什么”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“ThreadLocal的结构是什么”吧!

内容大纲

ThreadLocal的结构是什么

Java对象引用级别

在聊 ThreadLocal 前,先做前置知识铺垫,谈谈Java对象引用级别。

为了使程序能更灵活地控制对象生命周期,从 JDK1.2 版本开始,JDK把对象的引用级别由高到低分为强引用、软引用、弱引用、虚引用四种级别。

强引用 StrongReference

强引用是我们最常见的对象,它属于不可回收资源,垃圾回收器(后面简称G C)绝对不会回收它,即使是内存不足,J V M宁愿抛出 OutOfMemoryErrorM 异常,使程序终止,也不会来回收强引用对象。

ThreadLocal的结构是什么

软引用 SoftReference

如果对象是软引用,那它的性质属于可有可无,因为内存空间充足的情况下,G C不会回收它,但是内存空间紧张,G C发现它仅有软引用,就会回收该对象,所以软引用对象适合作为内存敏感的缓存对象。

ThreadLocal的结构是什么

只有对象仅被 WeakReference 引用,它才是弱引用级别对象,因为对象可以在多处被引用,所以 WeakReference 引用的对象,它可能在其他处被强引用了。

虚引用 PhantomReference

顾名思义,虚引用形同虚设,与其他几种引用不同,虚引用不会决定对象的生命周期。

如果一个对象仅有虚引用,那它就和没有任何引用一样,任何时候都可能被 G C 回收。

ThreadLocal的结构是什么

ThreadLocal

ThreadLocal很多地方叫线程本地变量,也有些地方叫线程本地存储,其实意思差不多。ThreadLocal为变量在每个线程中都创建了一个副本,每个线程可以访问自己内部的副本变量。

ThreadLocal是什么

Thread类声明了成员变量threadLocals,threadLocals才是真正的线程本地变量,因此每个 Thread 都有自己的线程本地变量,所以线程本地变量拥有线程隔离特性,也就是天生的线程安全。

ThreadLocal的结构是什么

从上图可以看到 threadLocals 成员变量类是 ThreadLocal.ThreadLocalMap,即是 ThreadLocal 提供的内部类,因此 Thread 线程本地变量的创建、新增、获取、删除实现核心,必然是围绕 threadLocals,所以开发者也是围绕 threadLocals 实现功能,为了后续重复使用,还会对代码实现进行封装复用,而 ThreadLocal 就是线程本地变量工具类,由 J D K 提供,线程本地变量的功能都已经实现好了,开箱即用,造福广大开发人员。

ThreadLocal常用的方法

  • set:为当前线程设置变量,当前ThreadLocal作为索引

  • get:获取当前线程变量,当前ThreadLocal作为索引

  • initialValue(钩子方法需要子类实现):赖加载形式初始化线程本地变量,执行get时,发现线程本地变量为null,就会执行initialValue的内容

  • remove:清空当前线程的ThreadLocal索引与映射的元素

ThreadLocal的结构是什么

现在总结出「本地线程变量的作用域,属于当前线程整个范围,一个线程可以跨越多个方法使用本地线程变量」,当你希望某些变量在某 Thread 的多个方法中共享 并保证线程安全,那就大胆的使用ThreadLocal(ps:一定要想清楚,是某个变量被Thread生命周期内多个方法共享,还是多个Thread共享这个变量!)。

ThreadLocal源码

先来看看User类实现的线程本地变量代码

ThreadLocal的结构是什么

通过上图,相信大伙对 ThreadLocalMap 结构已经非常清晰,不知有没有细心的小伙伴发现 ThreadLocal 竟被弱引用持有?

为什么ThreadLocal会被弱引用?这块疑惑后面会给大伙安排的明明白白,最后上一张 ThreadLocalMap 源码图。

ThreadLocal的结构是什么

步骤如下

  1. 获取当前线程

  2. 获取当前线程的本地变量

  3. 线程本地变量没有被创建,执行setInitialValue方法进行初始化,并返回value值

  4. 线程本地变量存在,ThreadLocal计算成索引从 本地线程变量 获取Entry,如果Entry为null,执行setInitialValue方法进行初始化,并返回value值,否则通过Entry获取value返回

initialValue方法

ThreadLocal的结构是什么

步骤如下

  1. 获取当前线程

  2. 获取线程本地变量

  3. 本地变量不为空,当前ThreadLocal为索引设置映射的value,否则创建线程本地变量再做后续的设置操作

remove 清除变量

ThreadLocal的结构是什么

为何采用弱引用

为什么 Entry 中对 ThreadLocal 使用弱引用?反问一句,如果使用强引用,会发生什么事情?

ThreadLocal的结构是什么

我们不知道 key 是什么,如何去获取映射的value,同样的道理,都没有入口去获取到ThreadContextTest.ThreadLoca,自然没办法获取映射的Entry元素。

设计中采用Map结构存储数据,却不能通过key去获取value,这设计明显不合理,又因key、value值是强引用,导致 G C 无法回收,造成内存溢出。

所以针对这种不合理的设计场景 J D K 做了优化,对 Entry 中的 ThreadLocal 使用弱引用,当 G C 发现它仅有弱引用的时候,会进行回收。

remove背后的意义

还没结束,上面留了个小尾巴,大伙都知道 Entry 中对 ThreadLocal 使用弱引用,但value是强引用,如果出现上面提到的不合理场景,value值无法清理,最终内存溢出。

其实value作为强引用设计属于合理,如果用软或弱引用,就出大问题了,程序跑着跑着突然get到了一个null,估计都得骂娘了,所以为解决内存溢出问题 J D K提供remove方法,使开发人员可以选择手动清理整个Entry元素,防止内存溢出。

还记的之前说过吗?线程本地变量的生命周期与线程绑定,一般线程的生命周期比较短,线程结束时,线程本地变量自然就销毁了,软引用与 remove 会不会有点多余了?

业务瞬息万变,大部分情况来说线程的生命周期比较短,但也业务场景会导致线程的生命周期较长,甚至可能线程无限循环执行,这些是你没办法预料到的,数量一旦上来很容易内存溢出,所以个人建议使用完之后及时清理ThreadLocal,理由如下

  • 生命周期较长的线程场景

  • 无限循环线程的场景

  • 线程池场景(因为线程池可以复用线程,而且公司使用的框架可能会定制化线程池,你不能保证他会在线程池内帮你remove)

到此,相信大家对“ThreadLocal的结构是什么”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI