SpringBoot之开线程并发时丢失请求上下文问题解决
当一个请求进来想要开线程并发处理时发现请求上下文丢失,代码如下,主线程可以正常获取header中的工号,但两个子线程则获取不到。
/**
* 运行线程
* @author sword
* @date 2022/3/1 20:23
*/
@GetMapping("/runThread")
@ResponseStatus(HttpStatus.OK)
@ApiOperation("运行线程")
public void runThread() throws ExecutionException, InterruptedException {
log.info("主线程开始");
log.info("主线程的header:{}", getUserCode());
// 开两个任务线程,均取不到request的header中的工号
CompletableFuture.allOf(
CompletableFuture.runAsync(() -> {
log.info("任务线程1开始");
log.info("任务线程1的header:{}", getUserCode());
})
.thenRun(() -> log.info("任务线程1结束")),
CompletableFuture.runAsync(() -> {
log.info("任务线程2开始");
log.info("任务线程2的header:{}", getUserCode());
})
.thenRun(() -> log.info("任务线程2结束"))
)
.get();
log.info("主线程结束");
}
/**
* 获取Request的Header中的userCode
* @return java.lang.String
* @author sword
* @date 2022/3/1 20:23
*/
private String getUserCode() {
return Optional.ofNullable((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.map(ServletRequestAttributes::getRequest)
.map(request -> request.getHeader("userCode"))
.orElse(null);
}
通过swagger调用结果如下:
2022-12-13 15:42:48.913 INFO 57812 --- [nio-8080-exec-3] c.s.d.c.interfaces.api.ConcurrentApi : 主线程开始
2022-12-13 15:42:48.913 INFO 57812 --- [nio-8080-exec-3] c.s.d.c.interfaces.api.ConcurrentApi : 主线程的header:000001
2022-12-13 15:42:48.914 INFO 57812 --- [onPool-worker-3] c.s.d.c.interfaces.api.ConcurrentApi : 任务线程2开始
2022-12-13 15:42:48.914 INFO 57812 --- [onPool-worker-2] c.s.d.c.interfaces.api.ConcurrentApi : 任务线程1开始
2022-12-13 15:42:48.914 INFO 57812 --- [onPool-worker-3] c.s.d.c.interfaces.api.ConcurrentApi : 任务线程2的header:null
2022-12-13 15:42:48.914 INFO 57812 --- [onPool-worker-2] c.s.d.c.interfaces.api.ConcurrentApi : 任务线程1的header:null
2022-12-13 15:42:48.914 INFO 57812 --- [onPool-worker-3] c.s.d.c.interfaces.api.ConcurrentApi : 任务线程2结束
2022-12-13 15:42:48.914 INFO 57812 --- [onPool-worker-2] c.s.d.c.interfaces.api.ConcurrentApi : 任务线程1结束
2022-12-13 15:42:48.914 INFO 57812 --- [nio-8080-exec-3] c.s.d.c.interfaces.api.ConcurrentApi : 主线程结束
造成这个问题的原因是 org.springframework.web.context.request.RequestContextHolder#getRequestAttributes 方法是通过 ThreadLocal 类来获取请求属性即 RequestAttributes 是线程隔离的,所以会出现主线程可以获取到 RequestAttributes 但子线程却获取不到的情况,代码如下:
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<>("Request context");
@Nullable
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}
既然已经知道由于线程隔离,子线程获取不到主线程的 RequestAttributes ,那么在开启子线程时为子线程设置从主线程获取的 RequestAttributes 即可解决问题,代码如下:
/**
* 使用ThreadLocal共享Request
* @author sword
* @date 2022/3/1 20:23
*/
@GetMapping("/shareRequestByThreadLocal")
@ResponseStatus(HttpStatus.OK)
@ApiOperation("使用ThreadLocal共享Request")
public void shareRequestByThreadLocal() throws ExecutionException, InterruptedException {
log.info("主线程开始");
final RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
log.info("主线程的header:{}", getUserCode());
// 开两个任务线程,先分别为两个线程设置主线程的RequestAttributes,然后再执行对应的任务,并等它们结束
CompletableFuture.allOf(
CompletableFuture.runAsync(() -> RequestContextHolder.setRequestAttributes(requestAttributes),
simpleExecutor)
.thenRun(() -> {
log.info("任务线程1开始");
log.info("任务线程1的header:{}", getUserCode());
})
.thenRun(() -> log.info("任务线程1结束")),
CompletableFuture.runAsync(() -> RequestContextHolder.setRequestAttributes(requestAttributes),
simpleExecutor)
.thenRun(() -> {
log.info("任务线程2开始");
log.info("任务线程2的header:{}", getUserCode());
})
.thenRun(() -> log.info("任务线程2结束"))
)
.get();
log.info("主线程结束");
}
2022-12-13 16:25:47.618 INFO 57812 --- [nio-8080-exec-5] c.s.d.c.interfaces.api.ConcurrentApi : 主线程开始
2022-12-13 16:25:47.618 INFO 57812 --- [nio-8080-exec-5] c.s.d.c.interfaces.api.ConcurrentApi : 主线程的header:00001
2022-12-13 16:25:47.619 INFO 57812 --- [ executor-1-1] c.s.d.c.interfaces.api.ConcurrentApi : 任务线程1开始
2022-12-13 16:25:47.619 INFO 57812 --- [ executor-1-1] c.s.d.c.interfaces.api.ConcurrentApi : 任务线程1的header:00001
2022-12-13 16:25:47.619 INFO 57812 --- [ executor-1-1] c.s.d.c.interfaces.api.ConcurrentApi : 任务线程1结束
2022-12-13 16:25:47.620 INFO 57812 --- [nio-8080-exec-5] c.s.d.c.interfaces.api.ConcurrentApi : 任务线程2开始
2022-12-13 16:25:47.620 INFO 57812 --- [nio-8080-exec-5] c.s.d.c.interfaces.api.ConcurrentApi : 任务线程2的header:00001
2022-12-13 16:25:47.620 INFO 57812 --- [nio-8080-exec-5] c.s.d.c.interfaces.api.ConcurrentApi : 任务线程2结束
2022-12-13 16:25:47.620 INFO 57812 --- [nio-8080-exec-5] c.s.d.c.interfaces.api.ConcurrentApi : 主线程结束