ThreadLocal

embedded/2025/3/18 12:07:55/

多线程和 ThreadLocal 是 Java 并发编程中的两个重要概念,它们在处理线程安全和资源隔离时扮演关键角色。

1. 多线程基础

1.1 什么是多线程?

  • 线程:是操作系统能够调度的最小执行单元,一个进程可以包含多个线程。

  • 多线程:指程序中存在多个线程并发执行,以提高 CPU 利用率或实现异步任务处理。

  • 并发问题:当多个线程共享同一资源时,可能引发竞态条件(Race Condition)、数据不一致等问题。

1.2 线程的生命周期

线程有以下状态:

  • New:线程被创建但未启动。

  • Runnable:线程正在执行或等待 CPU 时间片。

  • Blocked/Waiting:线程因等待锁、I/O 或条件变量而暂停。

  • Terminated:线程执行完毕或异常终止。

1.3 线程同步机制

为了解决并发问题,常用的同步工具有:

  • synchronized:通过锁机制保证代码块或方法的原子性。

  • Lock:如 ReentrantLock,提供更灵活的锁控制。

  • volatile:保证变量的可见性(但不保证原子性)。

  • Atomic 类:如 AtomicInteger,通过 CAS(Compare-and-Swap)实现无锁线程安全。

2. ThreadLocal的引入

在多线程环境中,共享变量的线程安全问题通常需要通过同步机制解决。但某些场景下,我们希望每个线程拥有自己的变量副本,避免共享,从而彻底消除同步需求,例如:

  • 数据库连接管理(每个线程独立连接)。
  • 用户会话信息(每个请求对应一个线程)。

2.1 什么是ThreadLocal?

ThreadLocal 是 Java 中用于实现线程封闭的工具类,它能为每个线程创建一个独立的变量副本,不同线程之间互不干扰。简单来说,ThreadLocal 让每个线程都能拥有自己的“私有”数据。

假设有一个储物柜(ThreadLocal),每个线程(人)都可以往自己的储物柜里存东西,且只能访问自己的储物柜,不会拿错别人的东西。

2.2 ThreadLocal的核心机制

1. 线程隔离
  • 每个线程内部有一个 ThreadLocalMap(类似Map结构),用来存储该线程的所有 ThreadLocal 变量。
  • 不同线程的 ThreadLocalMap 彼此独立,互不可见。
2. 存储结构
  • 每个 ThreadLocal 对象通过 threadLocalHashCode 唯一标识自己。
  • ThreadLocalMap 的 key 是 ThreadLocal 对象,value 是线程存储的值。

2.3 ThreadLocal的使用方法

1. 创建ThreadLocal变量

java">private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

2. 操作数据

java">// 设置值(仅当前线程可见)
threadLocal.set("Hello");// 获取值
String value = threadLocal.get();  // 输出 "Hello"// 删除值(防止内存泄漏)
threadLocal.remove();

注意事项:使用完ThreadLocal后必须调用remove()清除数据,防止内存泄漏。

2.4 ThreadLocal的典型使用场景

1. 线程级上下文管理:

        在Web应用中存储用户登录信息(如用户ID),避免在方法参数中层层传递。

java">public class UserContext {private static ThreadLocal<Long> userHolder = new ThreadLocal<>();public static void setUserId(Long userId) {userHolder.set(userId);}public static Long getUserId() {return userHolder.get();}public static void clear() {userHolder.remove();}
}
2. 数据库连接管理:

在事务中,保证一个线程的所有数据库操作使用同一个 Connection。

java">public class ConnectionHolder {private static ThreadLocal<Connection> connectionHolder = ThreadLocal.withInitial(() -> {return dataSource.getConnection();  // 初始化连接});public static Connection getConnection() {return connectionHolder.get();}public static void close() {connectionHolder.get().close();connectionHolder.remove();}
}
3. 日期格式化:

SimpleDateFormat 是线程不安全的,可用 ThreadLocal 为每个线程创建独立实例。

java">private static ThreadLocal<SimpleDateFormat> dateFormatHolder = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd")
);

3. ThreadLocal实现权限验证

使用 ThreadLocal 实现权限验证是一种常见的实践,尤其在 Web 应用中,比如用户登录后,后续的请求需要携带token,并在服务端验证权限。通过将用户身份信息(如用户ID、角色、权限列表)存储在 ThreadLocal 中,可以避免在方法参数中层层传递用户上下文,同时保证线程安全。可以在Spring Security或者自定义的拦截器中,获取用户信息后存入ThreadLocal,后续业务方法可以直接使用。

3.1 实现思路

  1. 拦截请求:在请求进入时(如通过过滤器或拦截器),解析Token或Cookie获取用户身份信息。
  2. 存储用户信息:将用户信息存入ThreadLocal,后续业务代码可直接从ThreadLocal中获取。
  3. 权限验证:在需要权限控制的地方(如Service层或自定义注解),从ThreadLocal取出用户信息,验证权限。
  4. 清理数据:请求结束时(如拦截器后置处理),清理ThreadLocal,防止内存泄漏。

3.2 代码实现

定义ThreadLocal上下文工具类:

java">public class UserContext {// 存储用户信息的 ThreadLocal(使用静态内部类初始化,避免内存泄漏)private static final ThreadLocal<LoginUser> userHolder = new ThreadLocal<>();// 设置当前用户public static void setUser(LoginUser user) {userHolder.set(user);}// 获取当前用户public static LoginUser getUser() {return userHolder.get();}// 清理用户信息(必须调用!)public static void clear() {userHolder.remove();}// 用户信息封装类(示例)public static class LoginUser {private Long userId;private String username;private List<String> roles;  // 角色列表(如 "admin", "user")private List<String> permissions;  // 权限列表(如 "order:create", "user:delete")// 省略构造方法、getter/setter}
}

拦截器中解析用户信息并存入ThreadLocal

java">public class AuthInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {// 1. 从请求头中获取 Token(示例)String token = request.getHeader("Authorization");// 2. 解析 Token 获取用户信息(实际需调用认证服务或查询数据库)LoginUser user = parseToken(token);// 3. 存入 ThreadLocalUserContext.setUser(user);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {// 请求结束后清理 ThreadLocalUserContext.clear();}// 模拟 Token 解析逻辑private LoginUser parseToken(String token) {// 实际需验证 Token 合法性,并查询用户权限(可结合 JWT 或 Redis)LoginUser user = new LoginUser();user.setUserId(1001L);user.setUsername("admin");user.setRoles(Arrays.asList("admin"));user.setPermissions(Arrays.asList("user:delete", "order:*"));return user;}
}

注册拦截器(Spring Boot配置)

java">@Configuration
public class WebConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new AuthInterceptor()).addPathPatterns("/**")  // 拦截所有请求.excludePathPatterns("/login");  // 排除登录接口}
}

在业务层进行权限验证

java">@Service
public class OrderService {public void deleteOrder(Long orderId) {// 从 ThreadLocal 获取当前用户LoginUser user = UserContext.getUser();// 1. 验证是否登录if (user == null) {throw new RuntimeException("未登录!");}// 2. 验证权限(示例:需要 "order:delete" 权限)if (!user.getPermissions().contains("order:delete")) {throw new RuntimeException("权限不足!");}// 3. 执行业务逻辑// ...}
}

3.3 注意事项

1. 内存泄漏:

        必须确保在请求结束时调用 UserContext.clear()(通常在拦截器的 afterCompletion 中清理)。

        如果使用线程池(如异步任务),线程会被复用,必须显式清理 ThreadLocal。

2. 分布式系统:

        ThreadLocal 只适用于单机环境。若服务是分布式的,用户信息应存储在 Redis 等共享存储中,通过 Token 解析获取。

3. 安全性:

        Token 解析逻辑需要严格防止伪造(如使用 JWT 签名或查询 Redis 校验有效性)。

4. 性能:

        频繁调用 ThreadLocal.get() 对性能影响极小,但权限列表不宜过大(建议控制在百级以内)。

3.4 适用场景

  • 单体架构需要Web应用(如Spring Boot后台管理系统)。
  • 需要减少方法参数传递的上下文信息(如用户身份)。
  • 配合AOP实现声明式权限控制。

4. 常见问题

4.1 ThreadLocal和synchronized的区别

  • ThreadLocal 通过空间换时间,每个线程独立操作数据,无需竞争锁。
  • synchronized 通过时间换空间,让线程排队访问共享数据。

4.2 子线程如何继承父线程的ThreadLocal数据

  • 使用 InheritableThreadLocal,但要注意线程池中复用线程时可能失效。

http://www.ppmy.cn/embedded/173588.html

相关文章

高亮动态物体——前景提取与动态物体检测器(opencv实现)

目录 代码说明 1. 导入库 2. 创建背景建模对象 3. 打开视频源 4. 逐帧处理视频 5. 应用背景建模获得前景掩码 6. 形态学操作去除噪声 6.1 定义形态学核 6.2 开运算去除噪点 6.3 膨胀操作填补前景区域空洞 7. 轮廓检测识别动态物体 8. 绘制轮廓和边界框 9. 显示处理…

Spring Boot 的自动装配

Spring Boot 的自动装配&#xff08;Auto Configuration&#xff09;是其核心特性之一&#xff0c;通过智能化的条件判断和配置加载机制&#xff0c;极大简化了传统 Spring 应用的配置复杂度。其原理和实现过程可概括为以下几个关键点&#xff1a; 一、核心触发机制&#xff1a…

HarmonyOS第26天:应用发布与推广全攻略:从0到1走向市场

一、引言:开启 HarmonyOS 应用之旅 在数字化时代的浪潮中,HarmonyOS 以其独特的分布式理念和强大的跨设备协同能力,为应用开发领域开辟了一片崭新的天地。随着 HarmonyOS 市场份额的稳步增长,其生态设备数量已突破 9 亿大关,吸引了超过 254 万开发者投身其中 ,成为了开发…

【操作系统安全】任务6:Linux 系统文件与文件系统安全 学习指南

目录 一、文件系统基础概念 二、查看文件系统信息 2.1 磁盘空间查看 2.2 分区与挂载管理 2.3 文件系统类型操作 三、文件系统权限配置 3.1 基础权限管理 3.2 所有权管理 3.3 特殊权限设置 四、文件操作基础 4.1 文件创建 4.2 文件删除 4.3 文件复制与移动 4.4 文件…

Python游戏开发自学指南:从入门到实践(第四天)

Python不仅适用于数据分析、Web开发和自动化脚本&#xff0c;还可以用于游戏开发&#xff01;虽然Python不是传统意义上的游戏开发语言&#xff0c;但其简洁的语法和丰富的库使其成为初学者学习游戏开发的绝佳选择。本文将为你提供一份全面的Python游戏开发自学指南&#xff0c…

dify数据库的操作方式二

dify数据库的操作方式 基础环境安装修改sanbox增加以来代码执行执行 基础环境安装 基础环境安装 修改sanbox增加以来 # 在线方式 # 修改volumes/sandbox/dependencies/python-requirements.txt mysql-connector-python# 离线方式 # 下载https://mirrors.aliyun.com/pypi/pac…

嵌入式Linux | 什么是 BootLoader、Linux 内核(kernel)、和文件系统?

01 什么是 BootLoader 呢&#xff1f; 它是个引导程序&#xff0c;也就是硬件复位以后第一个要执行的程序&#xff0c;它主要工作就是初始化操作系统运行的环境&#xff0c;比如说内存、定时器、缓冲器等&#xff0c;当这个工作做完以后&#xff0c;再把操作系统的代码加载…

使用 .NET Core 实现 RabbitMQ 消息队列的详细教程

RabbitMQ 是一个流行的消息队列中间件&#xff0c;它允许应用程序通过异步消息的方式进行通信。RabbitMQ 支持 AMQP 协议&#xff0c;可以通过多种方式与应用程序交互。在本教程中&#xff0c;我们将深入探讨如何在 .NET Core 环境中使用 RabbitMQ 来实现消息队列。我们将学习如…