从Dubbo的一次提交开始
故事得从前段时间翻阅 Dubbo 源码时,看到的一段代码讲起。
这段代码就是这个:
org.apache.dubbo.rpc.RpcContext
使用 InternalThreadLocal 提升性能。
相信作为一个程序猿,都会被 improve performance(提升性能)这样的字眼抓住眼球。
心里开始痒痒的,必须要一探究竟。
刚看到这段代码的时候,我就想:既然他是要提升性能,那说明之前的东西表现的不太好。
那之前的东西是什么?
经过长时间的推理、缜密的分析,我大胆的猜测到之前的东西就是:ThreadLocal。
来,带大家看一下:
果不其然,我真是太厉害了。
2018 年 5 月 15 日的提交:New threadLocal provides more performance. (#1745)
可以看到这次提交的后面跟了一个数字:1745。它对应一个 pr,链接如下:
https://github.com/apache/dubbo/pull/1745
在这个 pr 里面还是有很多有趣的东西的,出场人物一个比一个骚,文章的最后带大家看看。
能干啥用?
在说 ThreadLocal 和 InternalThreadLocal 之前,还是先讲讲它们是干啥用的吧。
InternalThreadLocal 是 ThreadLocal 的增强版,所以他们的用途都是一样的,一言蔽之就是:传递信息。
你想象你有一个场景,调用链路非常的长。当你在其中某个环节中查询到了一个数据后,最后的一个节点需要使用一下。
这个时候你怎么办?你是在每个接口的入参中都加上这个参数,传递进去,然后只有最后一个节点用吗?
可以实现,但是不太优雅。
你再想想一个场景,你有一个和业务没有一毛钱关系的参数,比如 traceId ,纯粹是为了做日志追踪用。
你加一个和业务无关的参数一路透传干啥玩意?
通常我们的做法是放在 ThreadLocal 里面,作为一个全局参数,在当前线程中的任何一个地方都可以直接读取。当然,如果你有修改需求也是可以的,视需求而定。
绝大部分的情况下,ThreadLocal 是适用于读多写少的场景中。
举三个框架源码中的例子,大家品一品。
第一个例子:Spring 的事务。
在我的早期作品《事务没回滚?来,我们从现象到原理一起分析一波》里面,我曾经写过:
Spring 的事务是基于 AOP 实现的,AOP 是基于动态代理实现的。所以 @Transactional 注解如果想要生效,那么其调用方,需要是被 Spring 动态代理后的类。
因此如果在同一个类里面,使用 this 调用被 @Transactional 注解修饰的方法时,是不会生效的。
为什么?
因为 this 对象是未经动态代理后的对象。
那么我们怎么获取动态代理后的对象呢?
其中的一个方法就是通过 AopContext 来获取。
其中第三步是这样获取的:AopContext.currentProxy();
然后我还非常高冷的(咦,想想就觉得羞耻)说了句:对于 AopContext 我多说几句。
看一下 AopContext 里面的 ThreadLocal:
调用 currentProxy 方法时,就是从 ThreadLocal 里面获取当前类的代理类。
那他是怎么放进去的呢?
我高冷的第二句是这样说的:
可以看到,经过一个 if 判断,如果为 true ,则调用 AopContext.setCurrentProxy 方法,把代理对象放到 AopContext 里面去。
而这个 if 判断的配置默认是 false,所以需要通过刚刚说的配置修改为 true,这样 AopContext 才会生效。
附送一个知识点给你,不客气。
第二个例子:mybatis 的分页插件,PageHelper。
使用方法非常简单,从官网上截个图:
这种情况下由于 param1 存在 null 的情况,就会导致 PageHelper 生产了一个分页参数,但是没有被消费,这个参数就会一直保留在这个线程上,也就是放在线程的 ThreadLocal 里面。
当这个线程再次被使用时,就可能导致不该分页的方法去消费这个分页参数,这就产生了莫名其妙的分页。
上面这个代码,应该写成下面这个样子:
这种写法,就能保证安全。
核心思想就一句话:只要你可以保证在 PageHelper 方法调用后紧跟 MyBatis 查询方法,这就是安全的。
因为 PageHelper 在 finally 代码段中自动清除了 ThreadLocal 存储的对象。
就算代码在进入 Executor 前发生异常,导致线程不可用的情况,比如常见的接口方法名称和 XML 中的不匹配,导致找不到 MappedStatement ,由于自动清除,也不会导致 ThreadLocal 参数被错误的使用。
所以,我看有的人为了保险起见这样去写: