技术背景:
spring boot+mybatis-plus做公共字段填充(MetaObjectHandler),之前项目没有接入用户系统故用户名填充都是使用的默认值,最近公司整合统一网关,接入用户信息,单例模式使用
ThreadLocal<Map<String, String>> threadLocal记录当前登陆用户信息,一切看似正常,用户信息获取都正常填充记录入库。
但是测试组细心的小姐姐发现有一个接口调用的过程中发生用户名被还原成默认值的情况,一开始还不信。仔细观察发现这个接口存入的用户最终都变成了默认用户名。
抓鸡。。。。。。。
为什么呢,debug发现整个流程没问题啊,出问题也只有可能自动填这里充出问题了,断点进去果然发现这个接口的实现方法进来后threadLocal变成了null,其他方法进来都是正常的,怎么会出现这种情况呢,怀疑人生中。。。。。。
突然眼前一亮发现有猫腻,这个方法是异步实现的@Async,当初因为这个方法后台执行比较耗时,所以采用异步的方式,而spring boot中使用@Async是通过线程池,想到这里就不奇怪了 这就涉及到主线程和子线程数据共享的问题了,刚好之前了解过,赶紧把ThreadLocal的实现换一下inheritableThreadLocals测试一波,呵呵果然。
追加内容:上面看似问题已解决,实际上还有坑,很隐蔽,一般测试难复现!!!
当使用@Async+自定义线程池+Threadlocal获取信息的时候要注意下面的情况
当核心线程数还没用完的时候,会创建新的线程,那么InheritableThreadLocal的值就会从父线程里面cpy,自然是没有问题,当我们在了线程中操作作InheritableThreadLocal是可以拿到数据。
但是如果有新的任务进来,只要核心线程有空闲,就会复用原先创建好的核心线程,这个时候,如果上一个使用过这个线程的子线程修改了InheritableThreadLocal,那么当前的子线程在使用lnheritableThreadLoca就会有问题了。因为这次是没有重行创建新线程,那么InheritableThreadLocal还是之前的InheritableThreadLocal。
应对这种情况
用Threadlocal的时候实现换成TransmittableThreadLocal就好了 不需要额外操作
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
<version>2.14.2</version>
</dependency>
关于Transmittable ThreadLocal的可以参考下面这篇文章