ThreadLocal 是什么鬼?用法、源码一锅端
threadlocal 是一个老生常谈的问题,在源码学习以及实际项目研发中,往往都能见到它的踪影,用途比较广泛,所以有必要深入一番。
敢问,threadlocal 都用到了哪里?有没有运用它去解决过业务问题呢?
没用过、答不上来也没关系,因为通过今天的分享,能让你轻松 get 如下几点,收获满满。
a)threadlocal 快速入门;
b)threadlocal 源码解读;
c)threadlocal 使用场景;
d)threadlocal 阿里规约中的奇技淫巧。
1. threadlocal 快速入门
理论暂且不谈,threadlocal 到底该怎么用?don't talk, show me the code!
上图是老项目真实在用的一个场景,主要借助 threadlocal 统计请求处理的耗时。仔细去看 threadlocal 使用起来其实蛮简单,接下来通过一段代码,让你快速掌握 threadlocal 的使用。
如上面代码所示,模拟一个业务请求处理耗时的场景,我们跑起来,看一看。
虽然代码能跑起来,充其量只是带你熟练使用了一把 threadlocal 的 api,并没有充分体会到 threadlocal 的核心设计理念。
看官别急,容我稍微修饰修饰代码,请看仔细。
代码调整很简单,就是把 main 方法中的代码,挪到线程体内去执行,然后看看获取请求开始时设置的时间值,是否会在多线程情况下而发生错乱?代码不会骗人的,跑起来看一看。
依据程序结果,就可以简单对 threadlocal 做个小结。
第一:对于 threadlocal 而言,最常用的 api,就是 get、set、remove,其实还有 initialvalue(常用来在创建 threadlocal 对象时设置初始值);
第二:针对程序输出的结果而言,站在线程的角度去看,就好像每一个线程都完全拥有 threadlocal 的变量,感觉就是为每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立的改变自己的副本,而不会和其它线程的副本发生冲突。
第三:坊间说 threadlocal 是 thread local variable(线程局部变量)的意思,或许将它命名为 threadlocalvar 会更加合适。
总结起来就一句「通过 threadlocal 能达到线程隔离的机制」,这句话真的对吗?其实是要持怀疑态度的。
don’t talk, show me the code!代码不会骗人的,拿出证据来。
上面代码是假想的一个场景,主要看代码。按照 threadlocal 的设计理念,会直接断言每个线程的序列号独立维护,互不影响。
可是结果却差点意思,居然没有达到线程隔离的效果,程序真实输出如下。
现象:当 threadlocal 设置的 value 都指向同一个对象(示例中的 flowno 对象),这个时候 threadlocal 就失灵啦(其实是有点难理解,没关系,后面有图解释)。
烟未灭,酒过半,是时候走进 jdk 源码看一看。
2. threadlocal 源码解读
首先从常用的 set 方法作为切入点,若搞懂这个方法,把 threadlocal 差不多就看穿啦。
如红色圈住部分代码,简单释义。
a)首先获取当前线程的对象 t;
b)然后获取 t 对应的成员变量 threadlocalmap;
c)接着判断 threadlocalmap 是否为空,不为空则将 threadlocal 和新的 value 放入到 threadlocalmap 中;
d)如果 threadlocalmap 为空,则对线程的成员变量 threadlocalmap 进行初始化操作,并将 threadlocal 和 value 放入 threadlocalmap 中。
哎呦,我去!threadlocal 刚用明白,这 threadlocalmap 又是什么鬼?别急,我们慢慢细看。
通过上面源码,可以清楚的知道 threadlocalmap 是 threadlocal 中的一个静态内部类,而 threadlocalmap 里面定义了一个静态的内部类 entry 来保存数据,在 entry 内部使用 threadlocal 作为 key,而 value 就是要设置的值(weakreference,稍微留意一下,后面会再次提及)。
说了这么多,感觉苦涩的文字,不如粗糙的图一张(想着点开篇的代码,说不定就醍醐灌顶啦,记住这个图就行啦)。
还记得开篇案例最后一个现象吗?当 threadlocal 设置的 value 都指向同一个对象,threadlocal 就失灵啦。
依据上图,如果设置的 value 初始值均都指向同一个对象时(指的是entry的value),多线程情况下,不发生影响才怪。
另外,对照着上面的图,再去看 get 方法,就相对好理解很多啦,不再贴代码,直接去看 remove 方法的源码。
remove 方法很简单,主要把 threadlocal 对象做为 key 从 threadlocalmap 清除对应的 entry。
remove 方法的用途在哪里?结合下面下面这个继承关系图去说说。
依据上图所示,很明显 entry 的 key 是一个 weakreference 弱引用(threadlocal 使用到了弱引用),极端情况下可能会发生内存泄露,所以代码上最终建议调用 remove 方法释放内存,避免发生内存泄露。
本次源码剖析就到这里,接下来我们看看 threadlocal 的主要使用场景。
3. threadlocal 使用场景
threadlocal 使用场景其实非常多,下面简单列举几个。
a) java 日志门面 org.sl4j.mdc 底层使用 threadlocal 来保证线程之间的数据隔离及数据传递;
b) hiberante 的session工具类 hibernateutil,借用 threadlocal 用于 session 管理(老项目还在用);
c)分布式链路跟踪;
d)类似项目研发中统计方法耗时,记录登录 session 信息,用户 id 等等;
e) jdk 7 之后提供的随机数生成器 threadlocalrandom,底层也借用 threadlocal 来实现。
4. threadlocal 阿里规约中的奇技淫巧
【强制】必须回收自定义的 threadlocal 变量,尤其在线程池场景下,线程经常会被复用, 如果不清理自定义的 threadlocal 变量,可能会影响后续业务逻辑和造成内存泄露等问题。尽量在代理中使用 try-finally 块进行回收。
正例: objectthreadlocal.set(userinfo); try { // ... } finally { objectthreadlocal.remove(); }
【参考】threadlocal 对象使用 static 修饰,threadlocal 无法解决共享对象的更新问题。
说明:这个变量是针对一个线程内所有操作共享的,所以设置为静态变量,所有此类实例共享此静态变量,也就是说在类第一次被使用时装载,只分配一块存储空间,所有此类的对象(只要是这个线程内定义的)都可以操控这个变量。
阿里开发规约对于 threadlocal 推荐使用约定,势必对你会有一定的参考价值。另外,继华山版之后泰山版的开发规约已经新鲜出炉啦,大家可以自行下载。
5. 写在最后
行文至此,接近尾声,本次主要带你对 threadlocal 进行快速入门,并通过剖析源码,带你知晓 threadlocal 背后的东西,最后对阿里开发规约中 threadlocal 的使用约定简单罗列,相信会对你实践有一定的指导意义。
本次分享就到这里,希望对你有所帮助吧。
一起聊技术、谈业务、喷架构,少走弯路,不踩大坑。会持续输出原创精彩分享,敬请期待!
上一篇: 自定义ProgressBar-油表,汽车速度表控件
下一篇: 这么快你就忘了