Threadlocal 和 ThreadLocalMap 原理解析

JAVA herman 1690浏览 0评论
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:xttblog,发送下载链接帮助你免费下载!
本博客日IP超过1800,PV 2600 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog,之前的微信号好友位已满,备注:返现
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
视频教程免费领

java 中提供了两个线程安全的操作方式,一个是 Threadlocal ,一个是ThreadLocalMap。本文介绍他们的区别和实现原理。

Threadlocal

Threadlocal 的属性包括 threadLocalHashCode,getMap(Thread t) 到当前线程去获取一个 ThreadLocalMap 的内部类对象,它的属性有一个 entry 数组,数组下标是当前 Threadlocal 对应的hash值,entry 对象是一个 WeakReference 对象,存放对应 Threadlocal 需要保存的变量值 value。

ThreadLocal 用于提供线程局部变量,通过它的 get() 和 set() 方法访问某个变量相应线程自己的局部变量。但在这里需要注意的是,它是通过各个线程自己创建对象然后调用这里的set方法设置进去,而不是由 ThreadLocal 自己来创建变量的副本的。

ThreadLocalMap

通过Entry数组保存局部变量。通过key(ThreadLocal类型)的hashcode来计算数组存储的索引位置i。如果i位置已经存储了对象,那么就往后挪一个位置依次类推,直到找到空的位置,再将对象存放。另外,在最后还需要判断一下当前的存储的对象个数是否已经超出了阈值(threshold的值)大小,如果超出了,需要重新扩充并将所有的对象重新计算位置。

线程保存ThreadLocalMap对象,对象主要通过Entry[]数组存放键{threadlocal}值,通过threadlocal的threadLocalHashCode定位存放数组位置,Entry extendsWeakReference<ThreadLocal> 的value保存变量副本,通过Entry.get获取threadlocal。

如果这里使用普通的key-value形式来定义存储结构,实质上就会造成节点的生命周期与线程强绑定,只要线程没有销毁,那么节点在GC分析中一直处于可达状态,没办法被回收,而程序本身也无法判断是否可以清理节点。弱引用是Java中四档引用的第三档,比软引用更加弱一些,如果一个对象没有强引用链可达,那么一般活不过下一次GC。当某个ThreadLocal已经没有强引用可达,则随着它被垃圾回收,在ThreadLocalMap里对应的Entry的键值会失效,这为ThreadLocalMap本身的垃圾清理提供了便利。

ThreadLocal

ThreadLocalMap维护了Entry环形数组,数组中元素Entry的逻辑上的key为某个ThreadLocal对象(实际上是指向该ThreadLocal对象的弱引用),value为代码中该线程往该ThreadLoacl变量实际塞入的值。

从ThreadLocal读一个值可能遇到的情况:根据入参threadLocal的threadLocalHashCode对表容量取模得到 index

  • 如果index对应的slot就是要读的threadLocal,则直接返回结果
  • 调用getEntryAfterMiss线性探测,过程中每碰到无效slot,调用expungeStaleEntry进行段清理;如果找到了key,则返回结果entry
  • 没有找到key,返回null

ThreadLocal的set方法可能会有的情况。

  • 探测过程中slot都不无效,并且顺利找到key所在的slot,直接替换即可
  • 探测过程中发现有无效slot,调用replaceStaleEntry,效果是最终一定会把key和value放在这个slot,并且会尽可能清理无效slot

    • 在replaceStaleEntry过程中,如果找到了key,则做一个swap把它放到那个无效slot中,value置为新值
    • 在replaceStaleEntry过程中,没有找到key,直接在无效slot原地放entry
  • 探测没有发现key,则在连续段末尾的后一个空位置放上entry,这也是线性探测法的一部分。放完后,做一次启发式清理,如果没清理出去key,并且当前table大小已经超过阈值了,则做一次rehash,rehash函数会调用一次全量清理slot方法也即expungeStaleEntries,如果完了之后table大小超过了threshold – threshold / 4,则进行扩容2倍

Java 中 还有一个 InheritableThreadLocal 。感兴趣的可以下去研究一下,我这里就不介绍了。还有 ThreadLocal 和 ThreadLocalMap 的源码,也建议大家看看!

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加QQ1群:135430763(2000人群已满),QQ2群:454796847(已满),QQ3群:187424846(已满)。QQ群进群密码:xttblog,想加微信群的朋友,之前的微信号好友已满,请加博主新的微信号:xttblog,备注:“xttblog”,添加博主微信拉你进群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作可添加助理微信进行沟通!

本文原文出处:业余草: » Threadlocal 和 ThreadLocalMap 原理解析