关于一个用户发送多次请求和tomcat的线程关系

tomcat默认使用线程池来管理线程,即当收到一个请求时,如果线程池存在空闲线程,则会从中取出一个空闲线程来处理该请求。当一个线程在处理请求时,其他请求就不会被分配至该线程。

例如有a,b,c三个空闲线程:
1、当request1到来时,分配a线程来处理
2、同时有request2到来,此时a线程在处理中,因此只有bc两个空闲线程,则会从中选一个处理request2。
3、request1处理完成,request3来了,则就可能分配给空闲线程a来处理。

问题来了,如果项目中使用了ThreadLocal,则在步骤1中会存入a线程对应的数据,步骤2中会存入b线程对应的数据,二者互不干扰。当到步骤3时,由于复用了线程a,因此request3这个请求处理过程中,可以直接通过ThreadLocal取到步骤1中放入的数据。

因此,在写代码时,注意在请求处理结束之前,把ThreadLocal中存入的数据清空,防止后续线程复用时造成干扰。

上案列(黑马点评):

在MvcConfig中配置了两个拦截器类

一个负责拦截一切路径 ,一个负责拦截需要登陆的路径。

通过设置order属性,让我们拦截一切路径的拦截器先执行

 preHandle在controller方法前执行

我们用户的数据(如果登陆了的话)现在存在redis中(过期时间默认30分钟)

然后就可以取出来存入threadLocal中

afterCompletion在controller方法完成后执行

用于清除threadLocal中的用户数据(对应第一段的理论)

package com.hmdp.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.UserDTO;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;

import static com.hmdp.utils.RedisConstants.LOGIN_USER_KEY;
import static com.hmdp.utils.RedisConstants.LOGIN_USER_TTL;

public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.获取请求头中的token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            return true;
        }
        // 2.基于TOKEN获取redis中的用户
        String key  = LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);
        // 3.判断用户是否存在
        if (userMap.isEmpty()) {
            return true;
        }
        // 5.将查询到的hash数据转为UserDTO
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);
        // 6.存在,保存用户信息到 ThreadLocal
        UserHolder.saveUser(userDTO);
        // 7.刷新token有效期
        stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);
        // 8.放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 移除用户
        UserHolder.removeUser();
    }
}

在第二个拦截器LoginInterceptor中

就可以从threadLocal中取出用户数据

package com.hmdp.utils;

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1.判断是否需要拦截(ThreadLocal中是否有用户)
        if (UserHolder.getUser() == null) {
            // 没有,需要拦截,设置状态码
            response.setStatus(401);
            // 拦截
            return false;
        }
        // 有用户,则放行
        return true;
    }
}

总结以下:

用户的每一次请求来之后,都对应tomcat中线程池的一个线程去执行。

可以利用threadLocal的隔离性,在线程的threadLocalMap中存入用户信息

并且用户登陆后信息也会保存在redis中以实现单点登陆

这两个地方清除用户数据的时机不同

threadLocal:用户每次请求结束后清除,不然可能当前这个tomcat的线程a保存有用户数据,然后别的用户也刚好用到了这个tomcat中的线程a,就发生了歧义

redis:用户数据过期后(30分钟),可以由拦截器刷新过期时间,这样我们的用户在过期时间内不管发送什么请求,都能在第一个拦截器中获取到,然后把它设置进threadLocal中

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值