java线程池threadlocal_线程池中使⽤ThreadLocal⽅案
尊重外国⼈写⽂章的习惯,如果你初次看到此类翻译可能会造成不愉悦,但如果你曾经看到过,那你⼀定明⽩我在说什么,有的地⽅加上我⾃⼰的理解和注释
在这篇⽂章⾥,我们将会演⽰如何从web线程⾥复制MDC数据到@Async注解的线程⾥,我们将会使⽤⼀个全新的 Spring Framework 4.3的特性: ThreadPoolTaskExecutor#setTaskDecorator() [set-task-decorator]. 下⾯是最终结果:
注意到倒数第⼆⾏和第三⾏:在这个log级别上输出了[userId:Duke],倒数第三⾏是在⼀个web线程⾥(⼀个使⽤@RestController注解的类)发出的,倒数第⼆⾏是在⼀个⽤了@Async注解的异步线程⾥发出的。本质上,MDC数据从web线程中复制到了使⽤@Async注解的异步线程⾥中了(这就是最酷的部分,:smirk:)
继续阅读吧,少年,去看看这是怎么实现的。这篇⽂章的所有代码都可以在GitGub上的⽰例中到。如
果有需要的话,可以去看看细节。
关于⽰例项⽬
这个⽰例项⽬基于Spring Boot 2。⽇志API这⾥⽤的是SLF4J和Logback(⽤了Logger, LoggerFactory和MDC) 如果你去看了那个⽰例项⽬,你将会发现这个@RestController注解的Controler
@RestController
public class MessageRestController {
private final Logger logger = Logger(getClass());
private final MessageRepository messageRepository;
MessageRestController(MessageRepository messageRepository) {
}
@GetMapping
List list() throws Exception {
logger.info("RestController in action");
return messageRepository.findAll().get();
}
}
注意到它输出了⽇志:RestController in action,同时注意到它有⼀个古怪的调⽤:messageRepository.findAll().get(),这是因为它执⾏了⼀个异步的⽅法,接收了⼀个Future对象,并且调⽤了get()⽅法来等待结果返回,所以这是⼀个在web线程⾥调⽤使⽤@Async注解的异步⽅法。这是⼀个很显然的⼈为的为了演⽰⽽写的⽰例(我猜你在⼯作中的⼀些场景中会明智的调⽤此类异步⽅法)
下⾯是那个repository类:
@Repository
class MessageRepository {
private final Logger logger = Logger(getClass());
@Async
Future> findAll() {
logger.info("Repository in action");
return new AsyncResult<>(Arrays.asList("Hello World", "Spring Boot is awesome"));
}
}
注意到findAll⽅法⾥打印了⽇志:Repository in action。
为了完整起见,让我向你展⽰如何在web线程⾥设置MDC数据的:
@Component
public class MdcFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
try {
MDC.put("mdcData", "[userId:Duke]");
chain.doFilter(request, response);
} finally {
MDC.clear();
}
}
}
如果我们什么也不做,我们可以在web线程⾥很轻松的拿到正确配置的MDC数据,但是当⼀个web请求进⼊了@Async注解的异步⽅法调⽤⾥,我们却不能跟踪它:MDC数据⾥的ThreadLocal数据不会简单的⾃动复制过来,好消息是这个超级简单解决
解决⽅案第⼀步: 配置@Async线程池
⾸先,定制化你的异步功能,我是这样做的:
@EnableAsync(proxyTargetClass = true)
@SpringBootApplication
public class Application extends AsyncConfigurerSupport {
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setTaskDecorator(new MdcTaskDecorator());
java线程池创建的四种
executor.initialize();
return executor;
}
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
有意思的地⽅是我们扩展了AsyncConfigurerSupport,好让我们可以⾃定义线程池
更精确的说:秘密在于executor.setTaskDecorator(new MdcTaskDecorator())。就是这⾏代码使我们可以⾃定义TaskDecorator
解决⽅案第⼆步: 实现TaskDecorator
现在到了说明⾃定义的TaskDecorator:
class MdcTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
// Right now: Web thread context !
// (Grab the current thread MDC data)
Map contextMap = CopyOfContextMap();
return () -> {
try {
// Right now: @Async thread context !
// (Restore the Web thread context's MDC data)
MDC.setContextMap(contextMap);
runnable.run();
} finally {
MDC.clear();
}
};
}
}
decorate()⽅法的参数是⼀个Runnable对象,返回结果也是另⼀个Runnable对象
这⾥,我只是把原始的Runnable对象包装了⼀下,⾸先取得MDC数据,然后把它放到了委托的run⽅法⾥(Here, I basically wrap the original Runnable and maintain the MDC data around a delegation to its run() method.英⽂原⽂是这样,太难翻译了,囧)
总结
从web线程⾥复制MDC数据到异步线程是如此的容易,这⾥展⽰的技巧不局限于复制MDC数据,你也可以使⽤它来复制其他ThreadLocal 数据(MDC内部就是使⽤ThreadLocal),或者你可以使⽤TaskDecorator做⼀些其他完全不同的事情:记录⽇志,度量⽅法执⾏的时间,吞掉异常,退出JVM等等,只要你喜欢
墙裂感谢Joris Kuipers (@jkuipers)提醒我这个⽜逼的Spring Framework 4.3新功能, An awesome tip :hugging:(这⼀句怎么翻译?)
参考
以下⾃⼰的总结:
使⽤ThreadLocal,不会在⼦线程中(包括new Thread和new线程池)获取到
使⽤InheritableThreadLocal,可以在⼦线程中(包括new Thread和new线程池)获取到,但是如果⽤的是线程池,⼀般不会每次使⽤的时候重新创建,⽽他的赋值只能在⾸次创建的时候可以(Thread类的inheritableThreadLocals变量),后⾯线程池中的线程重复使⽤时,⼀开始赋值的那个变量将会⼀直存在,你可能会得到错误的结果或者理解为这也是⼀种内存泄漏
在spring中,⼀般通过xml或者@Configuration来配置线程池,那么在项⽬启动的时候,线程池就完成
创建了,根本没有机会给你设置变量,所以最佳实践就是,在线程池提交任务的时候(execute和submit⽅法),把当前线程的threadlocal变量保存起来,重写run⽅法或者call⽅法,并且在调⽤实际的run⽅法前,保存刚才保存起来的变量,⼀般也是放到threadlocal⾥⾯,这样在实际的run⽅法⾥,就可以⽅便的通过threadlocal获取到了。
实现原理如上述3所说,这篇翻译的⽂章中也是该原理,ali提供了⼀个transmittable-thread-local,原理也是上⾯3所讲的,不过个⼈觉得它实现有点绕,⽤起来还算简单,可以⽤下
关于threadlocal的代码细节,见我的另外⼀篇⽂章:再看ThreadLocal
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论