ThreadLocal讲解与代码实践

原创
小哥 3年前 (2022-11-09) 阅读数 44 #大杂烩

一、概念

ThreadLocal是线程变量,即存款。ThreadLocal中的变量仅属于 当前线程唯一的变量,与其他线程隔离 ,ThreadLocal为每个线程创建一个副本以存储变量。

因为每个 Thread 有自己的实例副本,另一个 Thread 那么,无法访问 在多个线程之间不存在共享问题。 它与普通变量的不同之处在于,使用该变量的每个线程初始化实例的一个完全独立的副本。

2.适用场景

ThreadLocal变量适用于线程在方法或类之间隔离和共享的场景。

例如,当用户未登录时,需要为购物网站中的购物车功能提供一个临时购物车。id,对应于添加到购物车的项目。用户登录后,需要该用户。id和临时id整合购物车。此时,在进入购物车相关方法之前,需要在拦截器中判断用户是否已登录,并登录用户。id放入ThreadLocal,传递给相关Controller和Service方法使用。未登录就生成临时id。

3.实际代码

1.编写拦截器以识别用户是否已登录。
密钥代码:

  1. public static ThreadLocal threadLocal = new ThreadLocal<>();

  2. threadLocal.set(userInfoTo);

  3. threadLocal.get();

  4. (如果没有定义public static,或者使用线程池,您需要使用该调用。remove();要删除的方法)

    /**

    • @author guanghaocheng
    • @version 1.0
    • 翅膀被灰尘和雾气稍稍补充。,蜡烛末端的光线增加了太阳和月亮的辉光。
    • @date 2022/4/6 19:31
    • 在执行目标方法之前确定用户的登录状态,并将传递封装到controller目标请求 */

    public class CartInterceptor implements HandlerInterceptor {

    public static ThreadLocal threadLocal = new ThreadLocal<>();
    
    //在目标方法执行之前拦截
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        UserInfoTo userInfoTo = new UserInfoTo();
    
        HttpSession session = request.getSession();
        MemberRespVo member = (MemberRespVo) session.getAttribute(AuthServerConstant.LOGIN_USER);
        if(member != null){
            //用户已登录
            userInfoTo.setUserId(member.getId());
        }
    
        Cookie[] cookies = request.getCookies();
        if(cookies!=null && cookies.length>0){
            for (Cookie cookie : cookies) {
                String cookieName = cookie.getName();
                if(Objects.equals(cookieName,CartConstant.TEMP_USER_COOKIE_NAME)){
                    userInfoTo.setUserKey(cookie.getValue());
                }
            }
        }
        if (StringUtils.isEmpty(userInfoTo.getUserKey())){
            //这意味着没有临时用户,无论是否登录,临时用户都必须存在。
            String uuid = UUID.randomUUID().toString();
            userInfoTo.setUserKey(uuid);
        }
        //在执行目标方法之前,将数据放入。ThreadLocal在中,线程共享数据。
        threadLocal.set(userInfoTo);
        return true;//所有释放,只要你来到计量方法所有释放
    }
    
    //执行目标方法后:将临时用户分配给要保存的浏览器。
    
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        UserInfoTo userInfoTo = threadLocal.get();
        Cookie cookie = new Cookie(CartConstant.TEMP_USER_COOKIE_NAME, userInfoTo.getUserKey());
        cookie.setDomain("***.com");
        cookie.setMaxAge(CartConstant.TEMP_USER_COOKIE_TIMEOUT);
        response.addCookie(cookie);
    }

    }

2.注册拦截器.

@Configuration
public class mallWebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new CartInterceptor()).addPathPatterns("/**");
    }
}

三、在controller直接从ThreadLocal数据。

/**
 * @author guanghaocheng
 * @version 1.0
 * 翅膀被灰尘和雾气稍稍补充。,蜡烛末端的光线增加了太阳和月亮的辉光。
 * @date 2022/4/6 19:09
 *
 *
 * 登录:有session
 * 无登录:根据cookie中带的user-key执行临时用户标识
 * 第一次:如果没有临时用户,请帮助创建临时用户。
 * 如果您稍后登录,临时身份仍然存在,购物车应该与登录用户的购物车合并。
 */

@Controller
public class CartController {

    @GetMapping("/cart.html")
    public String cartListPage(HttpSession session){

        //从拦截器获取用户信息。
        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
        System.out.println(userInfoTo.toString());

        return "cartList";
    }

4.不能只在Controller中用,在Service如也可直接使用。只要是同一条线,你就可以得到它。

@Service
@Slf4j
public class CartServiceImpl implements CartService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private final String CART_PERFIX = "mall:cart";

    @Override
    public CartItem addToCart(Long skuId, Integer num) {

        //因为拦截器配置的执行。Controller在所有拦截程序之前,因此在请求中Controller还是Service可以直接获取threadlocal
        UserInfoTo userInfoTo = CartInterceptor.threadLocal.get();
        String cartkey = "";
        if(userInfoTo.getUserKey() != null){
            cartkey = CART_PERFIX + userInfoTo.getUserId();
        }else{
            cartkey = CART_PERFIX + userInfoTo.getUserId();
        }
        return null;
    }
}

4.注意事项

我们定义ThreadLocal通常使用变量。private static装饰因为当线程结束时 private static修饰ThreadLocal 相对实例副本可以自动回收。进入上面的例子。

如果没用private static修饰,您需要手动呼叫。remove();方法,清除该变量,否则将导致。 内存泄漏

ThreadLocal 的set方法维护ThreadLocalMap,实际上 ThreadLocalMap 中使用的 key 为 ThreadLocal 弱引用、弱引用的特点是,如果这个对象中只存在弱引用,那么它肯定会在下一次垃圾收集中被清除。
所以如果 ThreadLocal 如果没有外部强引用,它将在垃圾收集期间被清理,因此 ThreadLocalMap将此用于 ThreadLocal 的 key 它也将被清理。但是value 它是一个强大的参考,不会被清理,所以它会出现。 key 为 null 的 value。
ThreadLocal事实上,它是一个绑定到线程的变量,因此存在一个问题:如果没有ThreadLocal删除中的变量(remove或者替换,它的生命周期将与线程共存。通常,线程池中的线程管理使用线程重用。线程很难在线程池中结束甚至永不结束,这意味着线程的持续时间将是不可预测的,甚至是JVM生命周期是一致的。例如,如果ThreadLocal集合类或复杂对象每次都直接或间接地包装在同一个集合中。ThreadLocal在取出对象然后对内容进行操作之后,内部集合类和复杂对象所占用的空间可能会开始不断扩展。

版权声明

所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除

热门