背景
threadLocal默认做了线程数据隔离,本身就是避免数据竞争。 但是有时候会遇到数据共享问题,这里就需要用不同的实现类。
线程数据共享
工具类
简介
- ThreadLocal
父子线程间数据是隔离的,父线程不会传递threadLocal副本到子线程
- InheritatbleThreadLocal
子线程创建时,父线程threadLocal副本会传递给子线程(复用线程不会传递)。当threadLocal被主线程刷新时,但是线程池线程不会重新创建,只会复用,所以新threadLocal副本无法传递。
- TransmittableThreadLocal
阿里工具类,继承并加强于InheritatbleThreadLocal,解决了InheritableThreadLocal中线程复用传递副本的问题。
这里就有个大坑, 必须用阿里配套的线程池工具类包装下!!! 不然跟InheritatbleThreadLocal一个效果。
-
ThreadLocal
private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();
ExecutorService executorService = Executors.newFixedThreadPool(10);
PostMapping("/threadLocalAndExecutorService")
@ResponseBody
public Boolean threadLocalAndExecutorService(@RequestBody String tenantId) {
THREAD_LOCAL.set(tenantId);
for (int i = 0; i < 10; i++) {
executorService.submit( ()->{
String content = THREAD_LOCAL.get();
// 直接获取不到
log.info(">>> {} 中并行流线程名为 {},内容为 {}", tenantId,Thread.currentThread().getName(), content);
});
}
return true;
}
输出结果为
2021-12-09 09:38:35.856 INFO 26719 --- [pool-6-thread-4] c.y.u.controller.ThreadLocalController : >>> 7 中并行流线程名为 pool-6-thread-4,内容为 null
2021-12-09 09:38:35.856 INFO 26719 --- [pool-6-thread-3] c.y.u.controller.ThreadLocalController : >>> 7 中并行流线程名为 pool-6-thread-3,内容为 null
由于使用ThreadLocal, 父线程不会把threadLocal副本传递给子线程,所以子线程拿到的值都是null。
这里的子线程可以是
- 线程池中的复用线程
- 并行流中的新线程(并行流中第一个线程还是主线程,所以拿得到值)
-
InheritableThreadLocal
注意:如果你已经跑过上面的代码,请重启服务(重置线程池)
InheritableThreadLocal适用于子线程创建时将父线程中的threadLocal副本传递给子线程。
但是生产环境中用的线程池复用,这会导致新的threadLocal副本无法放入到子线程中。
private static final ThreadLocal<String> INHERITABLE_CONTEXT = new InheritableThreadLocal<>();
ExecutorService executorService = Executors.newFixedThreadPool(10);
@PostMapping("/inheritableAndExecutor")
@ResponseBody
public Boolean inheritableAndExecutor(@RequestBody String tenantId) {
log.info("参数传入 {}",tenantId);
INHERITABLE_CONTEXT.set(tenantId);
for (int i = 0; i < 10; i++) {
executorService.submit( ()->{
String content = INHERITABLE_CONTEXT.get();
//会出现串环境的问题
log.info(">>> 传入 {} 并行流线程名为 {},内容为 {} \n",tenantId,Thread.currentThread().getName(), content);
});
}
return true;
}
输出
pool-6-thread-1第一次能正常获取到值。
2021-12-09 09:41:04.851 INFO 27540 --- [pool-6-thread-1] c.y.u.controller.ThreadLocalController : >>> 传入 10 并行流线程名为 pool-6-thread-1,内容为 10
但是当第二次请求时,pool-6-thread-1线程没有获得到新的threadLocal副本值,这是InheritableThreadLocal的局限性。
2021-12-09 09:42:48.827 INFO 27540 --- [pool-6-thread-1] c.y.u.controller.ThreadLocalController : >>> 传入 4 并行流线程名为 pool-6-thread-1,内容为 10
-
TransmittableThreadLocal
阿里提供的一个工具包,在线程池等池化复用的执行组件情况下,提供ThreadLocal值的传递功能。解决异步上传时,上下文传递问题。同时解决了threadLocal刷新,线程池无法刷新的问题。
- 使用TransmittableThreadLocal实现类
- 使用TtlExecutors.getTtlExecutorService()获得线程池
ExecutorService ttlExecutorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(10));
private static final ThreadLocal<String> TRANSMITTABLE_THREAD_LOCAL = new TransmittableThreadLocal();
@PostMapping("/ttlAndTtlExecutorService")
@ResponseBody
public Boolean ttlAndTtlExecutorService(@RequestBody String tenantId) {
TRANSMITTABLE_THREAD_LOCAL.set(tenantId);
for (int i = 0; i < 10; i++) {
ttlExecutorService.submit( ()->{
String content = TRANSMITTABLE_THREAD_LOCAL.get();
//这里没有任何问题
log.info(">>> {} 中 ttl中并行流线程名为 {},内容为 {}", tenantId,Thread.currentThread().getName(), content);
});
}
return true;
}
注意:
如果不用包装类线程池,比如
- 直接创建线程池
- 并行流(parallelStream)
那么会有问题,跟InheritableThreadLocal一样,线程复用时,数据无法刷新。