測試代碼
/**
* 自定義 - 線程池
*/
private static final ThreadPoolExecutor executorService = new ThreadPoolExecutor(50, 200,
180L, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(3000), new ThreadFactory() {
final ThreadFactory defaultFactory = Executors.defaultThreadFactory();
@Override
public Thread newThread(Runnable r) {
Thread thread = defaultFactory.newThread(r);
thread.setName("testRequest - " + thread.getName());
return thread;
}
}, new ThreadPoolExecutor.CallerRunsPolicy());
private String getToken() {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
return null;
}
HttpServletRequest currentRequest = requestAttributes.getRequest();
return currentRequest.getHeader("token");
}
@SneakyThrows
public void test() {
String token = getToken();
log.info("測試1:主線程 mainThreadToken = {}", token);
log.info("----------------------------------------");
// 子線程1 Future
Future<String> future1 = CompletableFuture.supplyAsync(() -> {
// 子線程中獲取token
String childThreadToken = getToken();
log.info("測試2:子線程 childThreadToken = {}", childThreadToken);
return childThreadToken;
}, executorService);
future1.get();
log.info("----------------------------------------");
// 子線程2
Arrays.asList(1, 2, 3, 4).parallelStream().forEach(t -> {
// 子線程中獲取token
String childThreadToken = getToken();
log.info("測試3:子線程 childThreadToken = {}", childThreadToken);
});
log.info("----------------------------------------");
// 子線程 嵌套 子線程
Future<String> future2 = CompletableFuture.supplyAsync(() -> {
Arrays.asList(1, 2, 3, 4).parallelStream().forEach(t -> {
// 子線程中獲取token
String childThreadToken = getToken();
log.info("測試4:子線程 childThreadToken = {}", childThreadToken);
});
return null;
}, executorService);
future2.get();
log.info("----------------------------------------");
// 子線程 嵌套 子線程 傳遞 header 方式1
RequestAttributes attributes = RequestContextHolder.getRequestAttributes();
Future<String> future3 = CompletableFuture.supplyAsync(() -> {
try {
RequestContextHolder.setRequestAttributes(attributes);
Arrays.asList(1, 2, 3, 4).parallelStream().forEach(t -> {
RequestContextHolder.setRequestAttributes(attributes);
// 子線程中獲取token
String childThreadToken = getToken();
log.info("方式1:子線程 childThreadToken = {}", childThreadToken);
});
return null;
} finally {
RequestContextHolder.resetRequestAttributes();
}
}, executorService);
future3.get();
log.info("----------------------------------------");
// 子線程 嵌套 子線程 傳遞 header 方式2
Future<String> future4 = CompletableFuture.supplyAsync(() -> {
try {
Arrays.asList(1, 2, 3, 4).parallelStream().forEach(t -> {
// 子線程中獲取token
String childThreadToken = TokenInheritableThreadLocal.getToken();
log.info("方式2:子線程 childThreadToken = {}", childThreadToken);
});
return null;
} finally {
RequestContextHolder.resetRequestAttributes();
}
}, executorService);
future4.get();
log.info("----------------------------------------");
}
測試結果
--------------------------------------------------------------------------------
測試1:主線程 http-nio-8083-exec-3破喻,mainThreadToken = 123456
--------------------------------------------------------------------------------
測試2:子線程 testRequest - pool-1-thread-1掠兄,childThreadToken = null
--------------------------------------------------------------------------------
測試3:子線程 http-nio-8083-exec-3,childThreadToken = 123456
測試3:子線程 ForkJoinPool.commonPool-worker-2髓抑,childThreadToken = null
測試3:子線程 ForkJoinPool.commonPool-worker-9颤霎,childThreadToken = null
測試3:子線程 ForkJoinPool.commonPool-worker-2陕见,childThreadToken = null
--------------------------------------------------------------------------------
測試4:子線程 testRequest - pool-1-thread-2好爬,childThreadToken = null
測試4:子線程 ForkJoinPool.commonPool-worker-4,childThreadToken = null
測試4:子線程 ForkJoinPool.commonPool-worker-11奶卓,childThreadToken = null
測試4:子線程 ForkJoinPool.commonPool-worker-2一疯,childThreadToken = null
--------------------------------------------------------------------------------
方式1:子線程 ForkJoinPool.commonPool-worker-2,childThreadToken = 123456
方式1:子線程 ForkJoinPool.commonPool-worker-4夺姑,childThreadToken = 123456
方式1:子線程 ForkJoinPool.commonPool-worker-11墩邀,childThreadToken = 123456
方式1:子線程 testRequest - pool-1-thread-3,childThreadToken = 123456
--------------------------------------------------------------------------------
方式2:子線程 testRequest - pool-1-thread-4盏浙,childThreadToken = 123456
方式2:子線程 ForkJoinPool.commonPool-worker-2眉睹,childThreadToken = 123456
方式2:子線程 ForkJoinPool.commonPool-worker-11,childThreadToken = 123456
方式2:子線程 ForkJoinPool.commonPool-worker-4废膘,childThreadToken = 123456
--------------------------------------------------------------------------------
分析
- parallelStream() 也會導致請求頭丟失竹海,沒丟失的是其中一個并行的主線程 http-nio-8083-exec-3
- 通過RequestContextHolder.setRequestAttributes(attributes); 可以透傳header,但是麻煩
- InheritableThreadLocal 可以綁定父線程的變量丐黄,子線程也可以獲取斋配,使用方便
最佳解決方案
- TokenInheritableThreadLocal
public class TokenInheritableThreadLocal {
private TokenInheritableThreadLocal() {
}
public static final InheritableThreadLocal<String> THREAD_LOCAL = new InheritableThreadLocal() {
@Override
protected String initialValue() {
return null;
}
};
public static String getToken() {
return THREAD_LOCAL.get();
}
public static void remove() {
THREAD_LOCAL.remove();
}
}
public class TokenInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
TokenInheritableThreadLocal.THREAD_LOCAL.set(request.getHeader("token"));
return HandlerInterceptor.super.preHandle(request, response, handler);
}
/**
* 本次調用結束了,就可以釋放threadLocal資源避免內存泄漏的情況發(fā)生
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
TokenInheritableThreadLocal.THREAD_LOCAL.remove();
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
}
@Configuration
public class CommonWebAppConfigurer implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new TokenInterceptor());
}
}