转载

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 的使用。

ThreadLocal 是什么鬼?用法、源码一锅端

如上面代码所示,模拟一个业务请求处理耗时的场景,我们跑起来,看一看。

ThreadLocal 是什么鬼?用法、源码一锅端

虽然代码能跑起来,充其量只是带你熟练使用了一把 ThreadLocal 的 API,并没有充分体会到 ThreadLocal 的核心设计理念。

看官别急,容我稍微修饰修饰代码,请看仔细。

ThreadLocal 是什么鬼?用法、源码一锅端

代码调整很简单,就是把 main 方法中的代码,挪到线程体内去执行,然后看看获取请求开始时设置的时间值,是否会在多线程情况下而发生错乱?代码不会骗人的,跑起来看一看。

ThreadLocal 是什么鬼?用法、源码一锅端

依据程序结果,就可以简单对 ThreadLocal 做个小结。

第一:对于 ThreadLocal 而言,最常用的 API,就是 get、set、remove,其实还有 initialValue(常用来在创建 ThreadLocal 对象时设置初始值);

第二:针对程序输出的结果而言,站在线程的角度去看,就好像每一个线程都完全拥有 ThreadLocal 的变量,感觉就是为每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立的改变自己的副本,而不会和其它线程的副本发生冲突。

第三:坊间说 ThreadLocal 是 Thread Local Variable(线程局部变量)的意思,或许将它命名为 ThreadLocalVar 会更加合适。

总结起来就一句「 通过 ThreadLocal 能达到线程隔离的机制 」,这句话真的对吗?其实是要持怀疑态度的。

don’t talk, show me the code!代码不会骗人的,拿出证据来。

ThreadLocal 是什么鬼?用法、源码一锅端

上面代码是假想的一个场景,主要看代码。按照 ThreadLocal 的设计理念,会直接断言每个线程的序列号独立维护,互不影响。

可是结果却差点意思,居然没有达到线程隔离的效果,程序真实输出如下。

ThreadLocal 是什么鬼?用法、源码一锅端

现象:当 ThreadLocal 设置的 value 都指向同一个对象(示例中的 FlowNo 对象),这个时候 ThreadLocal 就失灵啦(其实是有点难理解,没关系,后面有图解释)。

烟未灭,酒过半,是时候走进 JDK 源码看一看。

2. ThreadLocal 源码解读

首先从常用的 set 方法作为切入点,若搞懂这个方法,把 ThreadLocal 差不多就看穿啦。

ThreadLocal 是什么鬼?用法、源码一锅端

如红色圈住部分代码,简单释义。

a)首先获取当前线程的对象 t;

b)然后获取 t 对应的成员变量 ThreadLocalMap;

c)接着判断 ThreadLocalMap 是否为空,不为空则将 ThreadLocal 和新的 value 放入到 ThreadLocalMap 中;

d)如果 ThreadLocalMap 为空,则对线程的成员变量 ThreadLocalMap 进行初始化操作,并将 ThreadLocal 和 value 放入 ThreadLocalMap 中。

哎呦,我去!ThreadLocal 刚用明白,这 ThreadLocalMap 又是什么鬼?别急,我们慢慢细看。

ThreadLocal 是什么鬼?用法、源码一锅端

通过上面源码,可以清楚的知道 ThreadLocalMap 是 ThreadLocal 中的一个静态内部类,而 ThreadLocalMap 里面定义了一个静态的内部类 Entry 来保存数据,在 Entry 内部使用 ThreadLocal 作为 key,而 value 就是要设置的值(WeakReference,稍微留意一下,后面会再次提及)。

说了这么多,感觉苦涩的文字,不如粗糙的图一张( 想着点开篇的代码,说不定就醍醐灌顶啦,记住这个图就行啦 )。

ThreadLocal 是什么鬼?用法、源码一锅端

还记得开篇案例最后一个现象吗?当 ThreadLocal 设置的 value 都指向同一个对象,ThreadLocal 就失灵啦。

依据上图,如果设置的 value 初始值均都指向同一个对象时(指的是Entry的value),多线程情况下,不发生影响才怪。

另外,对照着上面的图,再去看 get 方法,就相对好理解很多啦,不再贴代码,直接去看 remove 方法的源码。

ThreadLocal 是什么鬼?用法、源码一锅端

remove 方法很简单,主要把 ThreadLocal 对象做为 key 从 ThreadLocalMap 清除对应的 Entry。

remove 方法的用途在哪里?结合下面下面这个继承关系图去说说。

ThreadLocal 是什么鬼?用法、源码一锅端

依据上图所示,很明显 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 是什么鬼?用法、源码一锅端

ThreadLocal 是什么鬼?用法、源码一锅端

阿里开发规约对于 ThreadLocal 推荐使用约定,势必对你会有一定的参考价值。另外,继华山版之后泰山版的开发规约已经新鲜出炉啦,大家可以自行下载。

5. 写在最后

行文至此,接近尾声,本次主要带你对 ThreadLocal 进行快速入门,并通过剖析源码,带你知晓 ThreadLocal 背后的东西,最后对阿里开发规约中 ThreadLocal 的使用约定简单罗列,相信会对你实践有一定的指导意义。

本次分享就到这里,希望对你有所帮助吧。

一起聊技术、谈业务、喷架构,少走弯路,不踩大坑。会持续输出原创精彩分享,敬请期待!

原文  http://www.cnblogs.com/socoool/p/12779491.html
正文到此结束
Loading...