threadlocal跨线程传递解决方案(上)
1.在面试的过程中。经常会遇到面试官提到threadlocal的问题,很多情况下:
面试官最爱问的是:
threadlocal是做什么用的,用在哪些场景当中?
这个很多同学基本上也能提到:
ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。简单易懂的就是。每个线程有自己的数据副本,当线程结束后可以独立回收。
通常使用场景:
比如spring mvc收到请求。拦截器将用户id等数据放到threadlocal当中。在当前线程就可以直接拿到数据
具体例子:
有些同学会说出开源项目中有些地方会用到:
比如这种,比如mysql读写分离。主写读从过程中,类似于shardingsphere的中间件。就通过threadlocal来实现某一次读请求路由到主库。通过threadlocal设置标识即可
2.那么面试官继续问:
如果我启动另外一个线程。那么在主线程设置的threadlocal值能被子线程拿到吗?
如果拿不到,怎么去解决这个问题。有的同学一定会想这种场景比较少。顶多就是面试官找茬罢了
其实不然。这种场景非常普遍。举一个例子:
现在微服务基本上属于各个大厂小厂的标配。有一个问题。全链路如何来做监控。如果用过spring cloud的同学一定会说。zipkin,有的可能也会说cat,pinpoint,skywalking等等,
暂且不讨论这些全链路组件的优劣。在全链路组件落地的过程中,threadlocal是一个相当关键的步骤。拿zipkin的实现来说:
核心的数据传递都通过threadlocal来实现
那么回到我刚才的问题。threadlocal跨线程是否能够传递呢?那么我们做一个实验:
相关代码。我们在父线程设置了一个threadlocal。另外启动一个线程去获取
运行结果:
结论:拿不到数据。获取的是个null
那我们应该怎么去解决这个问题:
在java8中提供了一个这个类(InheritableThreadLocal):
从字面意思来看:可以实现父线程到子线程的共享,那么我们实验一下
将实现换成了InheritableThreadLocal
看一下效果 (果然解决了问题):
InheritableThreadLocal原理:
看一下目录结构:
复写了父类3个方法
当主线程中对该变量进行set操作的时候,和ThreadLocal一样会初始化一个ThreadLocalMap对实际的变量值进行存储,以ThreadLocal为key,值为value,如果有多个ThreadLocal变量也都是存储在这个Map中。该Map使用的是HashMap的原理进行数据的存储,但是和ThreadLocal有一点差别,因为其覆写了createMap的方法。
在Thread类当中,可以看出Thread类维护了两个成员变量,ThreadLocal以及InheritableThreadLocal,数据类型都是ThreadLocalMap.这也就解释了为什么这个变量是线程私有的。但是如果要知道为什么父子线程的变量传递,那就继续看一下源码。当我们在主线程中开一个新的子线程的时候,开始会new一个新的Thread
在thread构造函数中,new一个thread方法
可以看到,最后会调用ThreadLocal的createInheritedMap方法,而该方法会新建一个ThreadLocalMap,看一下构造函数的内容:
parentMap就是父线程的ThreadLocalMap,这个构造函数的意思大概就是将父线程的ThreadLocalMap复制到自己的ThreadLocalMap里面来,这样我们就可以使用InheritableThreadLocal访问到父线程中的变量了
但是这个就能解决问题了吗。并不是~
下个小结我会继续讲解InheritableThreadLocal在多线程调用过程中还存在哪些坑。如何解决