线程池传递上下文参数问题
多线程传递上下文变量问题
项目中一般都会通过ThreadLocal实现上下文变量,但是多线程环境中,ThreadLocal无法在父子线程中共享数据
public class ExecutorDemo001 {
public static final ThreadLocal<String> TL = new ThreadLocal<>();
public void foo() {
ThreadPoolTaskExecutor springPool = new ThreadPoolTaskExecutor();
springPool.setCorePoolSize(1);
springPool.setMaxPoolSize(2);
springPool.setKeepAliveSeconds(1000);
springPool.setQueueCapacity(10);
springPool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
springPool.initialize();
TL.set("aaa");
for (int i = 0; i < 100; i++) {
springPool.submit(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "---" + TL.get());
});
}
}
}
如上打印结果会出现
ThreadPoolTaskExecutor-1---null
ThreadPoolTaskExecutor-2---null
ThreadPoolTaskExecutor-1---null
main---aaa
ThreadPoolTaskExecutor-2---null
main---aaa
ThreadPoolTaskExecutor-1---null
ThreadPoolTaskExecutor-2---null
main---aaa
ThreadPoolTaskExecutor-1---null
main---aaa
ThreadPoolTaskExecutor-2---null
ThreadPoolTaskExecutor-1---null
ThreadPoolTaskExecutor-2---null
ThreadPoolTaskExecutor-1---null
ThreadPoolTaskExecutor-1---null
有三种解决方案
- JDK 提供的 InheritableThreadLocal
- 使用Spring线程池传递线程上下文
- Alibaba 提供的 TransmittableThreadLocal
JDK 提供的 InheritableThreadLocal
InheritableThreadLocal支持上下文传递,
原理是父线程创建子线程的时候,Thread 的 init 方法中如果查看到父线程内部有 InheritableThreadLocal 的数据。那就在子线程初始化的时,把父线程的 InheritableThreadLocal 拷贝给子线程。也就是说在线程池中无法使用

使用Spring线程池传递线程上下文
可以通过Spring线程池的装饰器实现线程执行前ThreadLocal变量的设置
同时为了防止内存泄漏(详见ThreadLocal搭配线程池导致内存泄漏),需要在线程池中的线程执行完后清理ThreadLocal变量
@Slf4j
public class ExecutorDemo001 {
public static final ThreadLocal<String> TL = new ThreadLocal<>();
public void foo() {
ThreadPoolTaskExecutor springPool = new ThreadPoolTaskExecutor();
springPool.setCorePoolSize(1);
springPool.setMaxPoolSize(2);
springPool.setKeepAliveSeconds(1000);
springPool.setQueueCapacity(10);
springPool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
springPool.setTaskDecorator(runnable -> {
String tl = TL.get();
return () -> {
try {
TL.set(tl);
runnable.run();
} catch (Exception e) {
log.error("decorate ctx...", e);
} finally {
TL.remove();
}
};
});
springPool.initialize();
TL.set("aaa");
for (int i = 0; i < 100; i++) {
springPool.submit(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("{}---{}", Thread.currentThread().getName(), TL.get());
});
}
log.info(String.valueOf(TL));
}
结果
main---aaa
ThreadPoolTaskExecutor-2---aaa
ThreadPoolTaskExecutor-1---aaa
ThreadPoolTaskExecutor-1---aaa
main---null
ThreadPoolTaskExecutor-2---aaa
ThreadPoolTaskExecutor-2---aaa
ThreadPoolTaskExecutor-1---aaa
main---null
ThreadPoolTaskExecutor-1---aaa
main---null
ThreadPoolTaskExecutor-2---aaa
可以看到,后续main方法执行的时候,获取不到主线程的TL变量数据了
因为如果阻塞队列满了,拒绝策略是主线程自己执行的时候,依然会调用装饰器的方法,这个时候主线程的上下文也会被清理掉,所以清理ThreadLocal的时候需要额外判断线程id是否跟主线程一致,如果一致则不能清理
@Slf4j
public class ExecutorDemo001 {
public static final ThreadLocal<String> TL = new ThreadLocal<>();
public void foo() {
ThreadPoolTaskExecutor springPool = new ThreadPoolTaskExecutor();
springPool.setCorePoolSize(1);
springPool.setMaxPoolSize(2);
springPool.setKeepAliveSeconds(1000);
springPool.setQueueCapacity(10);
springPool.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
springPool.setTaskDecorator(runnable -> {
String currentThreadName = getCurrentThreadName();
String tl = TL.get();
return () -> {
try {
TL.set(tl);
runnable.run();
} catch (Exception e) {
log.error("decorate ctx...", e);
} finally {
if (!getCurrentThreadName().equals(currentThreadName)) {
TL.remove();
}
}
};
});
springPool.initialize();
TL.set("aaa");
for (int i = 0; i < 100; i++) {
springPool.submit(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("{}---{}", Thread.currentThread().getName(), TL.get());
});
}
}
public static String getCurrentThreadName() {
Thread currentThread = Thread.currentThread();
return currentThread.getThreadGroup().getName() + "-" + currentThread.getName() + "-" + currentThread.getId();
}
}
运行结果
ThreadPoolTaskExecutor-2---aaa
main---aaa
ThreadPoolTaskExecutor-1---aaa
main---aaa
ThreadPoolTaskExecutor-2---aaa
ThreadPoolTaskExecutor-1---aaa
ThreadPoolTaskExecutor-1---aaa
ThreadPoolTaskExecutor-2---aaa
main---aaa
ThreadPoolTaskExecutor-1---aaa
ThreadPoolTaskExecutor-2---aaa
main---aaa
Alibaba 提供的 TransmittableThreadLocal
阿里的开源项目 TransmittableThreadLocal
参考
未完待续


浙公网安备 33010602011771号