首页 > 其他分享 >ThreadLocal详解及用法示例

ThreadLocal详解及用法示例

时间:2024-03-20 22:59:26浏览次数:15  
标签:变量 Thread 示例 threadLocal ThreadLocal 详解 线程 public

ThreadLocal概念

ThreadLocal 是Java并发包(java.util.concurrent)中提供的一个类,它的主要作用是在多线程环境下为每个线程提供一个独立的变量副本,使得每个线程在访问 ThreadLocal 时获取到的都是自己的私有变量,而不是共享的同一个变量。换句话说,ThreadLocal 能够隔离线程间的数据共享,提供线程级别的数据存储。

日常使用

在实际编程中,ThreadLocal 常用于以下场景:

  1. 线程上下文信息传递:例如在web应用中,服务器接收到请求后,需要在不同的过滤器、处理器链路中传递用户会话信息,此时可以将这些信息存放在 ThreadLocal 中,因为在Servlet容器中,每个HTTP请求都会被分配到一个单独的线程中处理。

  2. 避免同步开销:对于那些只需要在单个线程内保持状态,不需要线程间共享的数据,使用 ThreadLocal 可以避免使用锁带来的性能损耗。

  3. 数据库连接、事务管理:在多线程环境下,每个线程有自己的数据库连接,可以使用 ThreadLocal 存储当前线程的数据库连接对象,以确保线程安全。

实际开发中的用法

使用 ThreadLocal 主要涉及以下三个方法:

  • 初始化/设置值ThreadLocal.set(T value) 方法用来设置当前线程的变量副本值。
  • 获取值T get() 方法用来获取当前线程所对应的变量副本的值,如果此线程从未设置过值,那么返回 null 或者初始值(如果有的话)。
  • 移除值remove() 方法用于删除当前线程保存的变量副本,如果不主动清理,可能会造成内存泄露。

使用时需要注意的问题

​​​​​​​
  1. 内存泄露:当线程结束生命周期后,如果没有显式调用 remove() 方法,存储在线程本地变量表中的 ThreadLocal 变量副本不会自动删除,这可能导致它们无法被垃圾回收,尤其是在线程池场景中,如果线程会被复用,这个问题更为突出。

  2. 线程安全的误解:虽然 ThreadLocal 保证了每个线程只能访问自己的变量副本,但是它并不能保证变量副本本身的线程安全性,即如果存放在 ThreadLocal 中的对象不是线程安全的,多个线程通过各自的 ThreadLocal 访问相同的非线程安全对象时,还需要采取额外的同步措施。

  3. 过度使用:不恰当的使用 ThreadLocal 可能导致代码逻辑变得复杂,增加维护难度,尤其是当线程间本来就需要共享数据时,不应该滥用 ThreadLocal 避免数据交换。

使用中可能出现的事故

​​​​​​​​​​​​​​
  • 线程未正确清理:如果在使用完 ThreadLocal 之后忘记调用 remove() 方法,特别是在长期运行的后台线程或者线程池中,容易积累大量的废弃对象,造成内存泄漏。

  • 跨线程错误访问:在多线程环境中,如果不明确线程间的数据隔离,误以为 ThreadLocal 存储的数据在整个应用范围内可见,就会导致逻辑错误,因为每个线程看到的 ThreadLocal 值实际上是不同的。

  • 异常处理不当:在 try-finally 结构中,如果在 finally 块中没有正确清理 ThreadLocal,在抛出异常的情况下,可能会遗留未清理的资源。

简单用法示例:

import java.lang.ThreadLocal;

public class ThreadLocalExample {
    // 定义一个ThreadLocal变量,这里存储的是String类型
    public static final ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        // 创建两个线程并启动
        new Thread(() -> {
            // 设置当前线程的ThreadLocal变量
            threadLocal.set("Thread A");
            System.out.println("In Thread A: " + threadLocal.get());
        }).start();

        new Thread(() -> {
            // 设置当前线程的ThreadLocal变量,不影响其他线程
            threadLocal.set("Thread B");
            System.out.println("In Thread B: " + threadLocal.get());

            // 清理本线程的ThreadLocal变量
            threadLocal.remove();
            // 此时尝试获取已经移除的ThreadLocal变量,应该返回null
            System.out.println("After remove in Thread B: " + threadLocal.get());
        }).start();

        // 注意:主线程直接调用get()会报空指针异常,因为它没有设置过任何值
        // System.out.println("In Main Thread: " + threadLocal.get()); // 这行代码应避免运行,否则可能抛出NullPointerException
    }
}

示例解释:

  1. 我们首先创建了一个 ThreadLocal<String> 类型的静态变量 threadLocal
  2. 在每个线程内部,我们使用 set(String value) 方法给当前线程设置了一个本地变量的值。
  3. 同样在每个线程内部,我们使用 get() 方法来获取当前线程关联的本地变量的值。注意,每个线程只能获取到自己设置的那个值,相互之间不会干扰。
  4. 在第二个线程里展示了如何使用 remove() 方法清除当前线程的 ThreadLocal 变量。

常用用法示例:

在Web应用程序中,尤其是在基于线程模型的Servlet容器(如Tomcat)中,每个HTTP请求通常由一个单独的线程负责处理。

当用户登录时,为了能够在处理请求的过程中便捷地访问当前已登录用户的相关信息,而不必在各个服务层和控制器之间传递这些信息,可以利用ThreadLocal来存储用户的登录信息。

代码示例:

// CurrentUser类,封装了用户登录后的相关信息
public class CurrentUser {
    private String username;
    private String userId;
    // 其他用户属性...

    // 构造函数,getter和setter略...
}
// 创建一个ThreadLocal来存放CurrentUserInfo
public class UserContext {
    private static final ThreadLocal<CurrentUser> currentUserThreadLocal = new ThreadLocal<>();

    public static void setCurrentUser(CurrentUser user) {
        currentUserThreadLocal.set(user);
    }

    public static CurrentUser getCurrentUser() {
        return currentUserThreadLocal.get();
    }

    public static void clearCurrentUser() {
        currentUserThreadLocal.remove();
    }
}
// 在登录验证成功后,我们会在登录拦截器或者登录成功的处理逻辑中设置当前用户的上下文:
// 登录成功后设置ThreadLocal
public class LoginInterceptor implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        // 假设 authenticate(...) 方法完成了验证并返回了登录用户对象
        CurrentUser loggedInUser = authenticate(httpServletRequest);

        if (loggedInUser != null) {
            UserContext.setCurrentUser(loggedInUser);
        }

        try {
            chain.doFilter(request, response);
        } finally {
            // 请求处理完毕后清理ThreadLocal,防止内存泄漏
            UserContext.clearCurrentUser();
        }
    }
    
    // ...authenticate方法实现细节...
}
// 在需要用户上下文的地方,可以直接从ThreadLocal中获取:
public class UserService {
    public void processUserData() {
        CurrentUser currentUser = UserContext.getCurrentUser();
        if (currentUser != null) {
            // 处理当前登录用户的数据...
        }
    }
}

注意:

        通过这种方式,每个请求线程都会有自己的用户上下文,而且这个上下文仅对该线程可见,不会与其他线程混淆。同时,务必在请求处理完毕后清理 ThreadLocal 中的数据,以防止内存泄漏。在实际应用中,一般会配合Spring Security或者其他安全框架来处理这类用户上下文的管理。

标签:变量,Thread,示例,threadLocal,ThreadLocal,详解,线程,public
From: https://blog.csdn.net/Rcain_R/article/details/136891053

相关文章

  • gRPC简单示例
    gRPC概述gRPC是一种跨语言的RPC框架,之所以它能跨语言,是因为它基于protobuf描述对象实体和方法,最后通过protobuf编译器生成指定语言的代码。这样,就能通过一套protobuf声明生成多种语言的相同API,对于实现跨语言的RPC通信非常便利,同时也使用protobuf作为通信的序列化协议。如下通......
  • C++ 模板入门详解
    目录0.模板引入1.函数模板 1.函数重载的缺点 2.函数模板的概念和格式2. 函数模板的实例化 2.1 隐式实例化:让编译器根据实参推演模板参数的实际类型 2.2 显式实例化:在函数名后的<>中指定模板参数的实际类型2.3函数模板参数的匹配规则 3.类模板 3.1类......
  • 【Python从入门到精通】字符串详解
    Python不难学只要肯用心。  【Python从入门到精通】专栏课程:1、【Python从入门到精通】认识Python2、【Python从入门到精通】变量&数据类型3、【Python从入门到精通】列表&元组&字典&集合4、【Python从入门到精通】运算符&控制语句5、【Python从入门到精通】异常详......
  • C语言的指针详解
    一、指针的定义及使用1.指针是什么?指针是程序数据在内存中的地址,而指针变量是用来保存这些地址的变量。在同一CPU构架下,不同类型的指针变量所占用的存储单元长度是相同的,而存放数据的变量因数据的类型不同,所占用的存储空间长度也不同。有了指针以后,不仅可以对数据本身,也可以......
  • Java中String类型的创建与比较(详解)
    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录前言一、String类型是什么??二、String类型的创建使用字符串字面量使用new关键字intern()方法简读图解String的比较总结前言提示:这里可以添加本文要记录的大概内容:在背八股文(Holis版)的过程中遇......
  • 贪心算法详解
    贪心1建立数学模型来描述问题2把求解的问题分成若干个子问题3对每一个子问题求解,得到子问题的局部最优解4把子问题的解局部最优解合成原来解问题的一个解总结:局部最优做到全局最优。例题实战1在很久以前,有n个部落居住在平原上,依次编号为1到n,第i个部落的人数为ti,......
  • opengl日记10-opengl使用多个纹理示例
    文章目录环境代码CMakeLists.txt文件内容不变。fragmentShaderSource.fsvertexShaderSource.vsmain.cpp总结环境系统:ubuntu20.04opengl版本:4.6glfw版本:3.3glad版本:4.6cmake版本:3.16.3gcc版本:10.3.0在<opengl学习日记9-opengl使用纹理示例>的基础上,拓展使用多个纹......
  • MySQL varchar详解
    ......
  • 个人App上架步骤详解
    ​ 想要成功将个人开发的App上架到应用商店,需要经过一系列关键步骤,包括注册开发者账号、准备应用材料、提交审核等。以下将对这些步骤进行详细介绍。   ​一、注册开发者账号在将应用程序发布至应用商店之前,开发者需要注册开发者账号。目前,主要的应用商店包括苹果App......
  • nmap 参数详解。
    #读取文件扫描,一行一个,可以是主机名或者网段。nmap-iLtarget.txt#随机选择5个目标进行扫描。模拟对网络中随机主机的扫描,以便评估网络安全性。nmap-iR5#排除在扫描范围之外的主机或网络。nmap192.168.1.0/24--exclude192.168.1.1nmap192.168.1.0/24--exclude19......