异步线程中如何传递threadlocal中的信息

项目中一些RPC接口需要获取到用户信息才会允许调用,而当我们再一次请求中如果需要调A接口和B接口时,这两个接口业务并没有相关联,且耗时长,首选使用异步来完成,这时候就需要传递threadlocal中的登录信息。看了网上有用框架的,也有借助中间件的。最后想到了一个比较笨的方法,不使用第三方工具来实现这个功能,如果有需要改进的希望大家提出意见。

1.子线程中传递threadlocal的信息.

所谓子线程即直接在当前线程中new Thread.

Thread subThread = new Thread();

这种情况下传递threadlocal是很容易实现的,InheritableThreadLocal和Exchanger都可以实现,这里就不去讲使用方法了,大家可以自行去研究,但是这两个类在线程池中使用时就会出现问题。接下来讲讲springbooot项目中如何使得线程池中的线程能获取Http请求线程中threadlocal.

2.springbooot+线程池传递threadlocal

(1)先看目录结构:

 (2)LoginConfig:登录拦截配置类。这里是拦截所有请求

@Configuration
public class LoginConfig implements WebMvcConfigurer {


    @Autowired
    private LoginInerceptor loginInerceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInerceptor).addPathPatterns()
                .addPathPatterns("/**");
    }
}

(3)ThreadPoolConfig:注入一个MyThreadPoolExcutor,这是一个自定义线程池,后面会贴代码.

/**
 * 注入一个自定义线程池
 */
@Configuration
public class ThreadPoolConfig {


    @Bean(name = "myThreadPoolExcutor")
    public ThreadPoolExecutor getPoll(){
        return new MyThreadPoolExcutor(5,5,60, TimeUnit.SECONDS,new ArrayBlockingQueue<>(5));
    }
}

(4)LoginController:一个接收测试请求的Controller

@RestController
public class LoginController {

    @Autowired
    private IProduct product;

    @RequestMapping("/dosomething")
    public String asyncSearch(){
        return product.search();
    }
}

(5)LoginInerceptor:用户拦截请求,获取请求中的cookie信息,并且吧信息存入ThreadLocal中.

@Component
public class LoginInerceptor implements HandlerInterceptor {
    /**
     * 登录拦截
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        Cookie[] cookies = request.getCookies();
        User user = new User();

        if(cookies != null){
            for (int i = 0; i  <cookies.length ; i++) {
                Cookie cookie = cookies[i];
                String name = cookie.getName();
                if(name != null && name.equals("username"))
                    user.setUsername(cookie.getValue());

                if(name != null && name.equals("token"))
                    user.setToken(cookie.getValue());
            }
        }
        //把登录信息放入ThreadLocal中
        LoginUtil.putInfo(user);
        return true;
    }

(6)User:用户信息的封装,这里只加了两个字段.

/**
 * 用户信息
 */
public class User {

    private String username;

    private String token;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getToken() {
        return token;
    }

    public void setToken(String token) {
        this.token = token;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + token + '\'' +
                '}';
    }
}

(7)IProduct:这个代码就不贴了。就一个简单的测试方法

(8)IProductImpl:模拟业务处理方法,注入了自定义的线程池,并且在向线程池提交异步任务。

@Service
public class IProductImpl implements IProduct {
    
      
    @Resource
    private MyThreadPoolExcutor myThreadPoolExcutor;


    //此时线程为执行http请求的线程,当前线程已经在拦截时存放了登录信息。
    //现在模拟提交一个异步任务,并且让线程池的异步线程也拥有当前登录信息。
    @Override
    public String search() {
        //从当前http线程中获取登录信息
        User info = LoginUtil.getInfo();
        UserTask userTask = new UserTask();
        userTask.setUser(info);
        //提交线程池任务
        myThreadPoolExcutor.execute(userTask);
        return "请求完成";
    }
}

(9)UserTask:这个类是用于线程池提交任务的,并且这里面加入了user字段,用于保存登录用户信息,这个字段很重要

public class UserTask implements Runnable{

    //存放当前会话的用户信息
    private User user;

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }

    @Override
    public void run() {
        //模拟执行业代码
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

(10)LoginUtil:工具类,用于获取threadlocal信息

public class LoginUtil {

    final static ThreadLocal<User>  logoinInfo = new ThreadLocal();

    public static void putInfo(User user){
        logoinInfo.set(user);
    }

    public static User getInfo(){
        return logoinInfo.get();
    }

    public static void moveInfo(){
        logoinInfo.remove();
    }
}

(11)MyThreadPoolExcutor:自定义线程池。这里面最重要的一个方法beforeExecute。这个线程池执行任务前的一个回调函数。

/**
 * 自定义线程池
 */
public class MyThreadPoolExcutor extends ThreadPoolExecutor {


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


    /**
     * 线程池执行前会调用此方法
     * @param r
     * @param t
     */
    @Override
    public void beforeExecute(Thread t, Runnable r) {
         
        //这个任务就是我们传入的userTask任务,其中有个字段user就保存了用户登录信息
        if(r instanceof UserTask){
            UserTask userTask = (UserTask) r;
            User user = userTask.getUser();
            System.out.println("线程池的线程名字:"+t.getName());
            System.out.println("提交任务中用户信息:"+user.toString());
        }
    }
}

放一段ThreadPoolExcutor中的源码:

 final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    //提交任务前会执行此方法
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        //开始执行向线程池提交的任务
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        //执行完任务后调用这个方法
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }
/*Method invoked prior to executing the given Runnable in the given thread. This method is invoked by thread t that will execute task r, and may be used to re-initialize ThreadLocals, or to perform logging.
This implementation does nothing, but may be customized in subclasses. Note: To properly nest multiple overridings, subclasses should generally invoke super.beforeExecute at the end of this method.
Params:
t – the thread that will run task r
r – the task that will be executed
*/

protected void beforeExecute(Thread t, Runnable r) { }

可以发现这个方法在线程池中是一个空方法,t就是线程中执行任务的线程,r就是我们传入的任务.

(12)结果验证

利用postman发起请求,并在请求头中加入请求cookie.

 控制台打印:

 到此可以发现我们在线程池的线程中以userTask作为载体获取到了用户的登录信息。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值