首页 > 编程语言 >Java 中的线程本地存储(ThreadLocal)机制介绍

Java 中的线程本地存储(ThreadLocal)机制介绍

时间:2024-08-15 09:16:06浏览次数:7  
标签:存储 Java get threadLocal ThreadLocal 线程 public

Java 中的 ThreadLocal 是一个用于实现线程本地存储(Thread Local Storage, TLS)的机制。它可以为每个线程提供独立的变量副本,使得一个线程中的变量不受其他线程中的变量的影响。ThreadLocal 通常用于在多线程环境下避免线程之间共享数据,从而实现线程安全。

一、基本用法

ThreadLocal 类提供了一种机制,允许线程在本地存储一些变量,并在相同线程中获取这些变量。每个线程都有一个独立的 ThreadLocal 实例,可以保存自己的值,其他线程无法访问。

1. 创建和使用 ThreadLocal

// 创建一个 ThreadLocal 实例
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

// 在当前线程中设置值
threadLocal.set(100);

// 在当前线程中获取值
Integer value = threadLocal.get();

// 移除当前线程的值
threadLocal.remove();

每个线程在调用 set() 方法时,会将值存储在当前线程的 ThreadLocal 实例中,并且该值对其他线程不可见。同样地,get() 方法会返回当前线程存储的值。

2. 使用 initialValue() 初始化值

ThreadLocal 还可以通过重写 initialValue() 方法来设置初始值:

ThreadLocal<Integer> threadLocal = new ThreadLocal<>() {
    @Override
    protected Integer initialValue() {
        return 0;
    }
};

// 当前线程第一次调用 get() 时返回初始值 0
Integer initialValue = threadLocal.get();

这种方法可以避免每次使用前都调用 set() 方法来设置初始值。

3. 使用 ThreadLocal.withInitial() 简化初始化

Java 8 引入了 ThreadLocal.withInitial() 方法,允许使用 Lambda 表达式更简洁地设置初始值:

ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 1);
Integer initialValue = threadLocal.get();

二、ThreadLocal 的工作原理

1 . ThreadLocal 的工作机制

  • ThreadLocal 为每个线程独立存储数据,即每个线程都有自己独立的一份 ThreadLocal 数据副本。因此,线程之间不会共享 ThreadLocal 中的数据。
  • 当一个线程处理某个 HTTP 请求时,它会将数据存储到 ThreadLocal 中,其他线程无法访问或修改该数据。也就是说,ThreadLocal 保证了每个线程存储的数据是独立的,因此不会发生数据重复或相互覆盖的问题。

2. 并发请求中的表现

  • 在并发请求量大的情况下,由于每个请求都会分配到不同的线程处理,因此每个线程都维护自己的 ThreadLocal 变量,不会相互影响。

  • 但需要注意的是,线程池 的存在会引入一些复杂性。大多数应用服务器都会使用线程池来处理请求,即相同的线程可能会在不同时间处理不同的请求。

    • 例如,线程 A 处理了请求 1,然后它的 ThreadLocal 中保存了用户信息。处理完请求 1 后,如果不调用 clear(),线程 A 上存储的用户信息仍然存在。
    • 线程 A 可能会再次被分配去处理请求 2,此时如果没有正确清理 ThreadLocal,线程 A 上存储的用户信息可能会导致意外的行为,产生数据泄漏或错误。

    因此,在请求处理完毕后一定要确保调用 clear() 方法,以清理 ThreadLocal,防止线程池重用线程时出现数据污染的问题

三、注意事项

  1. 内存泄漏问题: 由于 ThreadLocalMap 的键是一个弱引用(WeakReference),而值是强引用,可能会导致内存泄漏问题。特别是在使用线程池时,线程不会被销毁,因此 ThreadLocal 的数据可能会长期存在内存中。为了避免这种问题,建议在线程使用完 ThreadLocal 后显式调用 remove() 方法清理数据。

  2. 线程池中的使用: 在使用线程池时,由于线程会被重用,必须特别小心 ThreadLocal 的使用。如果不在适当的时机清理 ThreadLocal,下一个任务可能会意外地获取到上一个任务的值。

  3. 性能问题:对于频繁创建和销毁线程的场景,ThreadLocal 的创建和销毁开销可能较大,因此更适合于线程池等长生命周期的线程管理场景

四、示例:存储与请求相关的数据,如当前登录用户的信息

1. 编写ThreadLocal工具类

理论上我们可以在Controller方法中,使用@RequestHeader获取JWT,然后在进行解析,如下

@Operation(summary = "获取登陆用户个人信息")
@GetMapping("info")
public Result<SystemUserInfoVo> info(@RequestHeader("Authorization") String token) {
    Claims claims = JwtUtil.parseToken(token);
    Long userId = claims.get("userId", Long.class);
    SystemUserInfoVo userInfo = service.getLoginUserInfo(userId);
    return Result.ok(userInfo);
}

上述代码的逻辑没有任何问题,但是这样做,JWT会被重复解析两次(一次在拦截器中,一次在该方法中)。为避免重复解析,通常会在拦截器将Token解析完毕后,将结果保存至ThreadLocal中,这样一来,我们便可以在整个请求的处理流程中进行访问了。

ThreadLocal概述

ThreadLocal的主要作用是为每个使用它的线程提供一个独立的变量副本,使每个线程都可以操作自己的变量,而不会互相干扰,其用法如下图所示。

common模块中创建com.atguigu.lease.common.login.LoginUserHolder工具类

public class LoginUserHolder {
    public static ThreadLocal<LoginUser> threadLocal = new ThreadLocal<>();

    public static void setLoginUser(LoginUser loginUser) {
        threadLocal.set(loginUser);
    }

    public static LoginUser getLoginUser() {
        return threadLocal.get();
    }

    public static void clear() {
        threadLocal.remove();
    }
}

同时在common模块中创建com.atguigu.lease.common.login.LoginUser

@Data
@AllArgsConstructor
public class LoginUser {

    private Long userId;
    private String username;
}

2. 修改AuthenticationInterceptor拦截器

@Component
public class AuthenticationInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String token = request.getHeader("Authorization");

        Claims claims = JwtUtil.parseToken(token);
        Long userId = claims.get("userId", Long.class);
        String username = claims.get("username", String.class);
        LoginUserHolder.setLoginUser(new LoginUser(userId, username));

        return true;

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        LoginUserHolder.clear();
    }
}

3. 编写Controller层逻辑

LoginController中增加如下内容

@Operation(summary = "获取登陆用户个人信息")
@GetMapping("info")
public Result<SystemUserInfoVo> info() {
    SystemUserInfoVo userInfo = service.getLoginUserInfo(LoginUserHolder.getLoginUser().getUserId());
    return Result.ok(userInfo);
}

4. 编写Service层逻辑

在`LoginService`中增加如下内容

```java
@Override
public SystemUserInfoVo getLoginUserInfo(Long userId) {
    SystemUser systemUser = systemUserMapper.selectById(userId);
    SystemUserInfoVo systemUserInfoVo = new SystemUserInfoVo();
    systemUserInfoVo.setName(systemUser.getName());
    systemUserInfoVo.setAvatarUrl(systemUser.getAvatarUrl());
    return systemUserInfoVo;
}
```

五、总结

ThreadLocal 提供了一种简单而有效的方式来为每个线程存储独立的数据,避免了线程间共享数据所导致的线程安全问题。然而,需要谨慎使用,尤其是在使用线程池或长生命周期线程时,以避免潜在的内存泄漏问题。

标签:存储,Java,get,threadLocal,ThreadLocal,线程,public
From: https://www.cnblogs.com/echohye/p/18360206

相关文章

  • Java数组篇[10]:数组的常见应用场景
    哈喽,各位小伙伴们,你们好呀,我是喵手。运营社区:C站/掘金/腾讯云/阿里云/华为云/51CTO;欢迎大家常来逛逛  今天我要给大家分享一些自己日常学习到的一些知识点,并以文字的形式跟大家一起交流,互相学习,一个人虽可以走的更快,但一群人可以走的更远。  我是一名后端开发爱好者......
  • Java中的代理模式(个人学习笔记)
    什么是代理代理是一种设计模式,提供了对目标对象另外的访问方式。(用户不需要直接访问目标对象,只需要接触代理对象就能实现访问)代理的好处目标对象可以被间接访问可以在目标对象实现的基础上实现额外的功能(除了目标对象提供的方法外,代理可以额外提供一些实用的方法),即扩展......
  • 【LeetCode:3148】矩阵中的最大得分(Java)
    题目链接3148.矩阵中的最大得分题目描述给你一个由正整数组成、大小为mxn的矩阵grid。你可以从矩阵中的任一单元格移动到另一个位于正下方或正右侧的任意单元格(不必相邻)。从值为c1的单元格移动到值为c2的单元格的得分为c2-c1。你可以从任一单元格开始......
  • java语言,MySQL数据库;电影推荐网站 30760(免费领源码)计算机毕业设计项目推荐万套实战教
    摘 要随着互联网时代的到来,同时计算机网络技术高速发展,网络管理运用也变得越来越广泛。因此,建立一个B/S结构的电影推荐网站;电影推荐网站的管理工作系统化、规范化,也会提高平台形象,提高管理效率。本电影推荐网站是针对目前电影推荐网站的实际需求,从实际工作出发,对过去的电影......
  • java语言,MySQL数据库;基于Web的高校知识共享系统设计与实现 32050(免费领源码)计算机毕业
    摘 要信息化社会内需要与之针对性的信息获取途径,但是途径的扩展基本上为人们所努力的方向,由于站在的角度存在偏差,人们经常能够获得不同类型信息,这也是技术最为难以攻克的课题。针对高校知识共享系统等问题,对高校知识共享系统进行研究分析,然后开发设计出高校知识共享系统以......
  • java语言,MySQL数据库;23825基于java的员工考勤系统(免费领源码)计算机毕业设计项目推荐万
    摘 要由于数据库和数据仓库技术的快速发展,员工考勤系统建设越来越向模块化、智能化、自我服务和管理科学化的方向发展。考勤管理系统对处理对象和服务对象,自身的系统结构,处理能力,都将适应技术发展的要求发生重大的变化。员工考勤系统除了具有共享系统的全部功能以外,能通过......
  • 轻松解析高频面试题: 线程设置数量多少合适?带你面试乱杀
    目录一、前言二、线程数和CPU利用率的小测试三、插入io操作四、线程数和CPU利用率总结五、线程数规划的公式 六、真实程序中的线程数一、前言相信很多小伙伴在刷面试题的时候都看到过一个线程数设置的理论:CPU密集型的程序-核心数+1I/O密集型的程序-核心数......
  • Java 代码本地设置Hadoop用户名密码
    在Hadoop环境中,通常使用Kerberos进行身份验证。但在一些开发或测试环境中,我们可能需要在本地代码中设置用户名和密码来模拟或进行简单的测试。虽然这不是一个安全的做法,因为它违背了Kerberos的使用原则,但在某些场景下(如单元测试或本地开发)可能是必要的。方法一:使用Hadoop的API来......
  • Java流量控制
    java流量控制用户交互ScannerJava.util.Scanner通过Scanner类来获取用户的输入Scanners=newScanner(System.in);next()一定要读取有效字符后才可以结束输入对输入有效字符之前遇到的空白,next()方法会自动将其去掉只有输入有效字符后才将其输入的空白作为分隔符......
  • [Java基础]collection
    数组与集合区别,用过哪些?数组和集合的区别:数组是固定长度的数据结构,一旦创建长度就无法改变,而集合是动态长度的数据结构,可以根据需要动态增加或减少元素。数组可以包含基本数据类型和对象,而集合只能包含对象。数组可以直接访问元素,而集合需要通过迭代器或其他方法访问元素。......