# demo-concurrent **Repository Path**: debug_life/demo-concurrent ## Basic Information - **Project Name**: demo-concurrent - **Description**: 开线程并发时丢失请求上下文问题解决 - **Primary Language**: Unknown - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2022-12-12 - **Last Updated**: 2022-12-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SpringBoot之开线程并发时丢失请求上下文问题解决 当一个请求进来想要开线程并发处理时发现请求上下文丢失,代码如下,主线程可以正常获取header中的工号,但两个子线程则获取不到。 ```java /** * 运行线程 * @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调用结果如下: ![运行线程](https://gitee.com/debug_life/demo-concurrent/raw/master/image/2022-12-13_153646_%E8%BF%90%E8%A1%8C%E7%A8%8B%E5%BA%8F.gif) ```log 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` 但子线程却获取不到的情况,代码如下: ```java private static final ThreadLocal requestAttributesHolder = new NamedThreadLocal<>("Request attributes"); private static final ThreadLocal inheritableRequestAttributesHolder = new NamedInheritableThreadLocal<>("Request context"); @Nullable public static RequestAttributes getRequestAttributes() { RequestAttributes attributes = requestAttributesHolder.get(); if (attributes == null) { attributes = inheritableRequestAttributesHolder.get(); } return attributes; } ``` 既然已经知道由于线程隔离,子线程获取不到主线程的 `RequestAttributes` ,那么在开启子线程时为子线程设置从主线程获取的 `RequestAttributes` 即可解决问题,代码如下: ```java /** * 使用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("主线程结束"); } ``` ![使用ThreadLocal共享Request](https://gitee.com/debug_life/demo-concurrent/raw/master/image/2022-12-13_162522_%E4%BD%BF%E7%94%A8ThreadLocal%E5%85%B1%E4%BA%ABRequest.gif) ```log 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 : 主线程结束 ``` [相关源码详见gitee](https://gitee.com/debug_life/demo-concurrent)