文章
问答
冒泡
自定义线程池传递Request变量

前言:

最近遇到一个问题,就是在做公司一些组件的时候,会将一些信息放置在请求头里,然后在不同的组件中获取实现不同的业务逻辑,但是在其他产品线接入的时候发现,当使用异步线程的时候,在线程任务中使用组件,request信息会丢失,导致获取的信息是空的导致结果不正确,这里记录下解决的过程。

1,情况模拟

由于中间件和业务代码过于复杂,就不完全复现了,这里就简单演示下

①,线程池定义

@Configuration
public class ThreadPoolConfig {

    @Bean
    public ThreadPoolExecutor threadPoolExecutor(){
        return new ThreadPoolExecutor(2, 2, 10L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(30));
    }
}

②,使用异步线程获取request信息

@RestController
@RequestMapping(value = "/api/request")
@RequiredArgsConstructor
public class TestController {

    private final ThreadPoolExecutor threadPoolExecutor;

    @GetMapping(value = "/thread")
    public String testThread(){
        threadPoolExecutor.execute(new Runnable() {
            @Override
            public void run() {
            HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
            String authorization = request.getHeader("Authorization");
                System.out.println("请求头信息为:"+ authorization);
            }
        });
        return "success";
    }
}

③,测试 简单定义两个请求,request.header里的Authorization分别对应不同值 

④,请求结果

请求正常返回了,但是异步线程中却报了这样一个错误,大概意思就是在当前线程中没有找到request,虽然我在中间件中有判断此情况,但是获取不到request,业务肯定是不正确的。

2,问题分析

的确,request是当前请求的线程生成的,属于当前请求线程的变量,在异步线程中尝试获取,是获取不到的,所以这里报了这个错误。

3,解决过程

①,首先我尝试将request变量直接透传到子线程中,刚开始没啥问题,但是多测试了几下,就发现request获取的值是不对的,已经混乱了。经过多放查找,最后在一个哥们写的文章的最后找到了答案,大概的意思就是说在容器处理request的过程中,request是会被复用的,这会导致如果主线程比子线程执行的早完成,那么此时request被复用下一个请求,子线程手里拿到的request其实就指向了内存中下一个请求的request了。这个方法不行。

②,其次我又查询文档,发现另外一种解决方案。

ServletRequestAttributes request = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
RequestContextHolder.setRequestAttributes(request, true);

但是这种方法同样有主线程先于子线程执行完的时候,request丢失的请求,也是不行。

4,于是我打算自己弄,尝试用ThreadLocal来做。

①,首先定义一个ThreadLocal副本

public class ThreadLocalMap {

    public final static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    
}

②,再定义一个线程类,此类是为了增强run方法,同时也是维护ThreadLocal副本变量内容。

    public class RequestThread extends Thread{

        RequestThread(Runnable target){
            super(target);
        }

        private String  authorization;

        @Override
        public void run() {
            ThreadLocalMap.threadLocal.set(authorization);
            super.run();
            ThreadLocalMap.threadLocal.remove();
        }
        public void setAuthorization(String authorization) {
            this.authorization = authorization;
        }
    }

③,再新增一个线程池子类,这里是为了能增强execute方法和submit方法,从主线程中获取request变量再透传下去,也是为了使用上面定义的RequestThread类包装一下原本要提交的task。

public class RequestThreadPoolExecutor extends ThreadPoolExecutor{
        public RequestThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        }

        @Override
        public <T> Future<T> submit(Runnable task, T result) {
            HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
            String authorization = request.getHeader("Authorization");
            RequestThread requestThread = new RequestThread(task);
            requestThread.setAuthorization(authorization);
            return super.submit(requestThread, result);
        }

        @Override
        public void execute(Runnable task){
            HttpServletRequest request = ((ServletRequestAttributes) (RequestContextHolder.currentRequestAttributes())).getRequest();
            String authorization = request.getHeader("Authorization");
            RequestThread requestThread = new RequestThread(task);
            requestThread.setAuthorization(authorization);
            super.execute(requestThread);
        }
    }

④,线程配置类,使用自定义线程池

@Configuration
public class ThreadPoolConfig {

    @Bean(value = "requestThreadPoolExecutor")
    public RequestThreadPoolExecutor threadPoolExecutor(){
       return new RequestThreadPoolExecutor(2, 2, 10L, TimeUnit.SECONDS, new ArrayBlockingQueue<>(30));
    }
}

⑤,测试一下,这里我提交了一个异步线程任务,在里面打印了从ThreadLocal获取的request存储内容,然后定时sleep随机20秒,是为了两个线程会随机哪个线程先执行完然后获取队列中的下一个request请求提交的任务。

@RestController
@RequestMapping(value = "/api/request")
@RequiredArgsConstructor
public class TestController {

    private final ThreadPoolConfig.RequestThreadPoolExecutor threadPoolExecutor;

    @GetMapping(value = "/thread")
    public String testThread(){
        threadPoolExecutor.execute(new Runnable() {
            @Override
            public void run() {
                String authorization = ThreadLocalMap.threadLocal.get();
                String name = Thread.currentThread().getName();
                System.out.println("时间:"+System.currentTimeMillis() + "当前线程:"+name + "获取变量:"+authorization);
                try {
                    Random r = new Random();
                    int i = r.nextInt(20000);
                    Thread.sleep(i);
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
        return "success";
    }
}

⑥,测试结果可以看到,我随机发送了rquest header 不同的信息,两个异步线程随机获取执行,结果无误。

 

ThreadLocal
线程池
requst

关于作者

Dane.shang
快30岁了还没去过酒吧
获得点赞
文章被阅读