第三个例子:Dubbo 的 RpcContext。
RpcContext 这个对象里面维护了两个 InternalThreadLocal,分别是存放 local 和 server 的上下文。
也就是我们说的增强版的 ThreadLocal:
作为一个 Dubbo 应用,它既可能是发起请求的消费者,也可能是接收请求的提供者。
每一次发起或者收到 RPC 调用的时候,上下文信息都会发生变化。
比如说:A 调用 B,B 调用 C。这个时候 B 既是消费者也是提供者。
那么当 A 调用 B,B 还是没调用 C 之前,RpcContext 里面保存的是 A 调用 B 的上下文信息。
当 B 开始调用 C 了,说明 A 到 B 之前的调用已经完成了,那么之前的上下文信息就应该清除掉。
这时 RpcContext 里面保存的应该是 B 调用 C 的上下文信息。否则会出现上下文污染的情况。
而这个上下文信息里面的一部分就是通过InternalThreadLocal存放和传递的,是 ContextFilter 这个拦截器维护的。
ThreadLocal 在 Dubbo 里面的一个应用就是这样。
当然,还有很多很多其他的开源框架都使用了 ThreadLocal 。
可以说使用频率非常的高。
什么?你说你用的少?
那可不咋的,人家都给你封装好了,你当个黑盒,开箱即用。
其实你用了,只是你不知道而已。
强在哪里?
前面说了 ThreadLocal的几个应用场景,那么这个 InternalThreadLocal 到底比 ThreadLocal 强在什么地方呢?
先说结论。
答案其实就写在类的 javadoc 上:
InternalThreadLocal 是 ThreadLocal 的一个变种,当配合 InternalThread 使用时,具有比普通 Thread 更高的访问性能。
InternalThread 的内部使用的是数组,通过下标定位,非常的快。如果遇得扩容,直接数组扩大一倍,完事。
而 ThreadLocal 的内部使用的是 hashCode 去获取值,多了一步计算的过程,而且用 hashCode 必然会遇到 hash 冲突的场景,ThreadLocal 还得去解决 hash 冲突,如果遇到扩容,扩容之后还得 rehash ,这可不得慢吗?
数据结构都不一样了,这其实就是这两个类的本质区别,也是 InternalThread 的性能在 Dubbo 的这个场景中比 ThreadLocal 好的根本原因。
而 InternalThread 这个设计思想是从 Netty 的 FastThreadLocal 中学来的。
本文主要聊聊 InternalThread ,但是我希望的是大家能学到这个类的思想,而不是用法。
首先,我们先搞个测试类:
public class InternalThreadLocalTest { private static InternalThreadLocal<Integer> internalThreadLocal_0 = new InternalThreadLocal<>(); public static void main(String[] args) { new InternalThread(() -> { for (int i = 0; i < 5; i++) { internalThreadLocal_0.set(i); Integer value = internalThreadLocal_0.get(); System.out.println(Thread.currentThread().getName()+":"+value); } }, "internalThread_have_set").start(); new InternalThread(() -> { for (int i = 0; i < 5; i++) { Integer value = internalThreadLocal_0.get(); System.out.println(Thread.currentThread().getName()+":"+value); } }, "internalThread_no_set").start(); } }
上面代码的运行结果是这样的:
首先是判断了传进来的 value 是否是 null 或者是 UNSET,如果是则调用 remove 方法。
null 是好理解的。这个 UNSET 是个什么鬼?
根据 UNSET 能很容易的找到这个地方:
原来是 InternalThreadLocalMap 初始化的时候会填充 UNSET 对象。
所以,如果 set 的对象是 UNSET,我们可以认为是需要把当前位置上的值替换为 UNSET,也就是 remove 掉。
而且,我们还看到了两个关键的信息:
1.InternalThreadLocalMap 虽然名字叫做 Map ,但是它挂羊头卖狗肉,其实里面维护的是一个数组。
2.数组初始化大小是 32。
接着我们回去看 else 分支的逻辑:
调用的是 InternalThreadLocalMap 对象的 get 方法。
而这个方法里面的两个 get 就有趣了。
能走到 fastGet 方法的,说明当前线程是 InternalThread 类,直接可以获取到类里面的 InternalThreadLocalMap。
如果走到 slowGet 了,则回退到原生的 ThreadLocal ,只是在原生的里面,我还是放的 InternalThreadLocalMap: