用这四招优雅地实现 Spring Boot 异步线程间数据传递

网站建设3年前发布
34 0 0

Spring Boot 自定义线程池实现异步开发相信看过陈某的文章都了解,但是在实际开发中需要在父子线程之间传递一些数据,比如用户信息,链路信息等等,比如用户登录信息使用ThreadLocal存放保证线程隔离,代码如下:,那么子线程想要获取这个LoginVal如何做呢?,今天就来介绍几种优雅的方式实现Spring Boot 内部的父子线程的数据传递。,20230307013927d4ff2b40904e70411b1890196e3d421c0bb954978,每执行一次异步线程都要分为两步:,代码如下:,虽然能够实现目的,但是每次开异步线程都需要手动设置,重复代码太多,看了头疼,你认为优雅吗?,TaskDecorator是什么?官方api的大致意思:这是一个执行回调方法的装饰器,主要应用于传递上下文,或者提供任务的监控/统计信息。,知道有这么一个东西,如何去使用?,TaskDecorator是一个接口,首先需要去实现它,代码如下:,这里我只是设置了LoginVal,实际开发中其他的共享数据,比如SecurityContext,RequestAttributes....,TaskDecorator需要结合线程池使用,实际开发中异步线程建议使用线程池,只需要在对应的线程池配置一下,代码如下:,此时业务代码就不需要去设置子线程的值,直接使用即可,代码如下:,来看一下结果,如下图:,20230307013928a1e306250bc12fa36b0581742922d58b9fb12d818,这里使用的是CompletableFuture执行异步任务,使用@Async这个注解同样是可行的。,注意:无论使用何种方式,都需要指定线程池,这种方案不建议使用,InheritableThreadLocal虽然能够实现父子线程间的复用,但是在线程池中使用会存在复用的问题。,这种方案使用也是非常简单,直接用InheritableThreadLocal替换ThreadLocal即可,代码如下:,TransmittableThreadLocal是阿里开源的工具,弥补了InheritableThreadLocal的缺陷,在使用线程池等会池化复用线程的执行组件情况下,提供​​ThreadLocal​​值的传递功能,解决异步执行时上下文传递的问题。,使用起来也是非常简单,添加依赖如下:,OauthContext改造代码如下:,从定义来看,TransimittableThreadLocal继承于InheritableThreadLocal,并实现TtlCopier接口,它里面只有一个copy方法。所以主要是对InheritableThreadLocal的扩展。,在TransimittableThreadLocal中添加holder属性。这个属性的作用就是被标记为具备线程传递资格的对象都会被添加到这个对象中。,要标记一个类,比较容易想到的方式,就是给这个类新增一个Type字段,还有一个方法就是将具备这种类型的的对象都添加到一个静态全局集合中。之后使用时,这个集合里的所有值都具备这个标记。,应用代码是通过TtlExecutors工具类对线程池对象进行包装。工具类只是简单的判断,输入的线程池是否已经被包装过、非空校验等,然后返回包装类ExecutorServiceTtlWrapper。根据不同的线程池类型,有不同和的包装类。,进入包装类ExecutorServiceTtlWrapper。可以注意到不论是通过ExecutorServiceTtlWrapper#submit方法或者是ExecutorTtlWrapper#execute方法,都会将线程对象包装成TtlCallable或者TtlRunnable,用于在真正执行run方法前做一些业务逻辑。,所以,重点的核心逻辑应该是在TtlCallable#call()或者TtlRunnable#run()中。以下以TtlCallable为例,TtlRunnable同理类似。在分析call()方法之前,先看一个类Transmitter。,进入​​TtlCallable#call()​​方法。,到这基本上线程池方式传递本地变量的核心代码已经大概看完了。总的来说在创建TtlCallable对象是,调用capture()方法捕获调用方的本地线程变量,在call()执行时,将捕获到的线程变量,替换到线程池所对应获取到的线程的本地变量中,并且在执行完成之后,将其本地变量恢复到调用之前。,上述列举了4种方案,陈某这里推荐方案2和方案4,其中两种方案的缺点非常明显,实际开发中也是采用的方案2或者方案4。

© 版权声明

相关文章