首页 > 其他分享 >ThreadLocal线程重用时带来的问题

ThreadLocal线程重用时带来的问题

时间:2024-09-11 13:02:43浏览次数:1  
标签:重用 信息 用户 ThreadLocal 线程 result currentUser

背景

我们都知道ThreadLocal实现了资源在线程内独享,线程之间隔离
实际使用中,ThreadLocal适用于变量在线程间隔离,而在方法或类间共享的场景。比如用户信息,当用户信息需要在多个方法之间传递或者共享使用的时候,同时,每个Tomcat请求的用户信息是私有的。这时可使用ThreadLocal,即直接从线程的ThreadLocal中获取用户信息,而不用在方法的形参中获取。
但是,不当的使用也会带来一些问题,比如在线程池中使用ThreadLocal可能会导致获取到ThreadLocal中的历史数据,造成数据错乱。如Tomcat中使用的多线程是线程池实现的,如果不当使用ThreadLocal, 由于线程重用,就会有该数据错乱问题。本篇博文会举例说明线程重用时,ThreadLocal变量会造成什么影响,并给出合适的解决方案。

案例说明

使用 Spring Boot 创建一个 Web 应用程序,使用 ThreadLocal 存放一个 Integer 的值,来暂且代表需要在线程中保存的用户信息,这个值初始是 null。
在业务逻辑中,我先从 ThreadLocal 获取一次值,然后把外部传入的参数设置到 ThreadLocal 中,来模拟从当前上下文获取到用户信息的逻辑,随后再获取一次值,最后输出两次获得的值和线程名称。

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping(value = "threadlocal")
public class ThreadLocalTestClass {

    private ThreadLocal<Integer> currentUser =  ThreadLocal.withInitial(() -> null);

    @GetMapping(value = "wrong")
    public Map wrong(@RequestParam("id") Integer userId) {
        // 设置用户信息之前先查询一次ThreadLocal中的用户信息
        String before  = Thread.currentThread().getName() + ":" + currentUser.get();
        // 设置用户信息到ThreadLocal
        currentUser.set(userId);
        // 设置用户信息之后,再查询一次ThreadLocal中的用户信息
        
        String after  = Thread.currentThread().getName() + ":" + currentUser.get();
        // 汇总输出两次查询结果
        Map result = new HashMap();
        result.put("before", before);
        result.put("after", after);
        return result;  
    }
}

执行结果

图1

图2

图2的正确结果应该是 null, 2。但是此时却访问到了请求1的用户信息。并未实现请求1和请求2的隔离性!

执行结果说明

 * 以上方法中我们将tomcat的最大线程数设置为1。故只有一个线程在工作,所以线程会重用。
 * 由于我们使用了ThreadLocal, 每次请求完以后,线程中存放的ThreadLocal设置的值(本例是用户信息)依然存在。
 * 所以就可能导致另一个用户的请求打进来以后,从ThreadLocal中获取到的用户信息是上一个用户的信息。
 * 解决办法比较简单,就是在代码的 finally 代码块中,显式清除 ThreadLocal中的数据。
 * 这样一来,新的请求过来即使使用了之前的线程也不会获取到错误的用户信息了。

配置文件

正确的代码写法

 @GetMapping(value = "right")
    public Map wrong(@RequestParam("id") Integer userId) {
        // 设置用户信息之前先查询一次ThreadLocal中的用户信息
        String before  = Thread.currentThread().getName() + ":" + currentUser.get();
        // 设置用户信息到ThreadLocal
        currentUser.set(userId);
        // 设置用户信息之后,再查询一次ThreadLocal中的用户信息
        try {
            String after  = Thread.currentThread().getName() + ":" + currentUser.get();
            // 汇总输出两次查询结果
            Map result = new HashMap();
            result.put("before", before);
            result.put("after", after);
            return result;
        } finally {
            // 返回设置的新值以后,将Threadlocal中的用户信息清除,防止被下一个请求访问到,造成脏数据。
            currentUser.remove();
        }
    }

正确的结果


这样的话,即使线程重用了,每个Tomcat请求也不会访问到其他请求设置的值了,就不会出现数据错乱问题。

标签:重用,信息,用户,ThreadLocal,线程,result,currentUser
From: https://www.cnblogs.com/xyuanzi/p/18408063

相关文章

  • 【高级编程】认识Java多线程 代码举例三种创建线程的方式
    文章目录主线程创建线程方式1:Thread方式2:Runnable方式3:Callable进程:应用程序的执行实例,有独立的内存空间和系统资源线程:CPU调度和分派的基本单位,进程中执行运算的最小单位,可完成一个独立的顺序控制流程多线程:如果在一个进程中同时运行了多个线程,用来完成不同的工......
  • 进程 vs 线程
    进程vs线程好的,让我们通过对比的方式来详细说明进程和线程之间的区别,以便更好地理解它们各自的特性和适用场景。进程vs.线程1.资源拥有与共享进程:每个进程都有自己的独立地址空间和资源(如文件句柄、内存映射等)。进程之间的资源是隔离的,这意味着一个进程中的修改不会......
  • C#笔记9 对线程Thread的万字解读 小小多线程直接拿下!
    上一条笔记有些潦草,这是因为昨天并没有很好的理解线程可以进行的操作。今天准备细化自己对这方面的理解和记录。来看看细节吧!环境:VS2022系统:windows10环境:.Net8.0以及.NetFrameWork4.7.2(winform)线程是什么?线程是什么?每个操作系统上运行的应用程序都是一个进程,一个......
  • 对上篇文章线程安全问题的具体解决策略实现细节的详细阐述:
    1.竞态条件的解决策略实现细节使用synchronized关键字:publicclassCounter{privateintcount=0;//同步方法publicsynchronizedvoidincrement(){count++;//这个操作现在是原子的}publicsynchronizedin......
  • day10-配置文件&日志&多线程
    一、配置文件1.1properties配置文件properties配置文件特点:1、都只能是键值对2、键不能重复3、文件后缀一般是.properties结尾的​Properties这是一个Map集合(键值对集合),但是我们一般不会当集合使用主要用来代表属性文件,通过Properties可以读写属性文件里的......
  • [Java并发]线程安全的List
    线程安全的List目前比较常用的构建线程安全的List有三种方法:使用Vector容器使用Collections的静态方法synchronizedList(List<T>list)采用CopyOnWriteArrayList容器使用Vector容器Vector类实现了可扩展的对象数组,并且它是线程安全的。它和ArrayList在常用方法的实现上很......
  • WPF UI线程死锁的各种场景
    WPFUI线程死锁的场景通常出现在多线程操作时,特别是当后台线程试图与UI线程交互、更新界面或同步执行任务时。如果没有正确处理线程间的资源访问或同步问题,UI线程可能会被阻塞,导致界面无响应。以下是常见的WPFUI线程死锁场景,以及如何避免这些问题的建议。1.使用Dispatche......
  • C++ 多线程详解:从基础到应用
    目录一、什么是多线程?二、C++中的多线程支持三、总结在现代应用中,多线程成为了提升程序性能的重要工具。特别是当我们希望充分利用多核CPU的计算能力时,C++提供了强大的多线程支持,可以并发地执行多个任务。今天,我们将通过易懂的讲解与实际的代码示例,帮助你掌握C+......
  • 线程池以及详解使用@Async注解异步处理方法
    目录一.什么是线程池:二.使用线程池的好处:三.线程池的使用场景:四.使用线程池来提高Springboot项目的并发处理能力:1.在application.yml配置文件中配置:2.定义配置类来接受配置文件内的属性值:3.启用异步支持:4.实例: 五.详细解析@Async注解的使用:1.@Async注解作用:2.@Asyn......
  • 面试官:如何实现线程池任务编排?
    任务编排(TaskOrchestration)是指管理和控制多个任务的执行流程,确保它们按照预定的顺序正确执行。1.为什么需要任务编排?在复杂的业务场景中,任务间通常存在依赖关系,也就是某个任务会依赖另一个任务的执行结果,在这种情况下,我们需要通过任务编排,来确保任务按照正确的顺序进行执......