多线程之ThreadLocal在tomcat中运行的并发问题

ThreadLocal在tomcat中运行的并发问题

我们知道,ThreadLocal 适用于变量在线程间隔离,而在方法或类间共享的场景。如果用户信息的获取比较昂贵(比如从数据库查询用户信息),那么在 ThreadLocal 中缓存数据是比较合适的做法。
使用 Spring Boot 创建一个 Web 应用程序,使用 ThreadLocal 存放一个 Integer 的值,来暂且代表需要在线程中保存的用户信息,这个值初始是 null。在业务逻辑中,我先从 ThreadLocal 获取一次值,然后把外部传入的参数设置到 ThreadLocal 中,来模拟从当前上下文获取到用户信息的逻辑,随后再获取一次值,最后输出两次获得的值和线程名称。

@Controller
public class ThreadLocalTest {
    private static final ThreadLocal<Integer> currentUser = ThreadLocal.withInitial(() -> null);
    @GetMapping("wrong")
    @ResponseBody
    public Map wrong(@RequestParam("userId") 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;
    }
}

当我们使用tomcat 时,在并发场景下,threadLocal 会出现诡异的赋值,为了更快复现问题,我们将tomcat 工作线程设置为1

server.tomcat.max-threads=1

运行程序后先让用户 1 来请求接口,可以看到第一和第二次获取到用户 ID 分别是 null 和 1,符合预期:
在这里插入图片描述
再使用用户2:
在这里插入图片描述
这次就出现了 Bug,第一和第二次获取到用户 ID 分别是 1 和 2,显然第一次获取到了用户 1 的信息,原因就是 Tomcat 的线程池重用了线程。从图中可以看到,两次请求的线程都是同一个线程:http-nio-8080-exec-1。
这个例子告诉我们,在写业务代码时,首先要理解代码会跑在什么线程上。
使用类似 ThreadLocal 工具来存放一些数据时,需要特别注意在代码运行完后,显式地去清空设置的数据。如果在代码中使用了自定义的线程池,也同样会遇到这个问题。
修正后的代码如下:

@Controller
public class ThreadLocalTest {
    private static final ThreadLocal<Integer> currentUser = ThreadLocal.withInitial(() -> null);
    @GetMapping("wrong")
    @ResponseBody
    public Map wrong(@RequestParam("userId") Integer userId) {
        //设置用户信息之前先查询一次ThreadLocal中的用户信息
        String before  = Thread.currentThread().getName() + ":" + currentUser.get();
        //设置用户信息到ThreadLocal
        currentUser.set(userId);
        Map result;
        try {
            //设置用户信息之后再查询一次ThreadLocal中的用户信息
            String after  = Thread.currentThread().getName() + ":" + currentUser.get();
            //汇总输出两次查询结果
            result = new HashMap();
            result.put("before", before);
            result.put("after", after);
        } finally {
            currentUser.remove();
        }
        return result;
    }
}

重新运行程序可以验证,再也不会出现第一次查询用户信息查询到之前用户请求的 Bug。
在这里插入图片描述
此文章为学习使用,不作为商业用途,整理自极客时间,如有侵权,请联系删除

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
多线程环境,我们可以使用ThreadLocal类来实现线程局部变量。ThreadLocal为每个线程提供了一个独立的存储空间,每个线程都可以独立地修改自己的副本,互不干扰。 要在多线程环境使用ThreadLocal,我们需要执行以下步骤: 1. 创建ThreadLocal对象:通过创建ThreadLocal对象来保存线程的局部变量。例如,可以使用`ThreadLocal<Integer>`来保存一个整数类型的线程局部变量。 2. 设置线程局部变量:在每个线程,通过调用ThreadLocal对象的`set()`方法来设置该线程的局部变量的值。例如,使用`threadLocal.set(value)`来设置线程局部变量的值为value。 3. 获取线程局部变量:在每个线程,通过调用ThreadLocal对象的`get()`方法来获取该线程的局部变量的值。例如,使用`threadLocal.get()`来获取线程局部变量的值。 4. 移除线程局部变量(可选):如果需要,在每个线程结束时,可以通过调用ThreadLocal对象的`remove()`方法来移除该线程的局部变量。例如,使用`threadLocal.remove()`来移除线程局部变量。 下面是一个示例代码,演示如何在多线程环境使用ThreadLocal: ```java public class ThreadLocalExample { private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { Thread thread1 = new Thread(() -> { threadLocal.set(1); System.out.println("Thread 1: " + threadLocal.get()); threadLocal.remove(); }); Thread thread2 = new Thread(() -> { threadLocal.set(2); System.out.println("Thread 2: " + threadLocal.get()); threadLocal.remove(); }); thread1.start(); thread2.start(); } } ``` 这个示例,我们创建了一个ThreadLocal对象来保存整数类型的线程局部变量。在每个线程,我们使用`threadLocal.set()`方法来设置线程局部变量的值,并使用`threadLocal.get()`方法来获取线程局部变量的值。最后,我们调用`threadLocal.remove()`方法来移除线程局部变量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值