Java里的线程神器:ThreadLocal

今天我们要学习一种在JAVA线程中至关重要的类------ThreadLocal。

ThreadLocal是一个强大的JAVA类,它能实现线程局部变量的功能。通过ThreadLocal,每一个线程都可以拥有自己的一份变量副本,互相之间不会影响操作,真正做到数据隔离。尤其在多线程环境下,避免了线程间混乱的数据共享、避免出现竞争条件,使得编程更加优雅。

ThreadLocal通常用来解决线程安全性问题,比如在Web应用中,你可以把数据库连接、用户身份信息等放在ThreadLocal里,确保每个线程都能独自访问这些信息,互不干涉。除此之外,ThreadLocal还常用于实现线程封闭(Thread Confinement)的模式,将数据完全限制在单个线程内,极大地简化了并发编程的难度。

我个人工作经历里,确实用得比较少,可能因为我的工作与之关系不大。但认识这个类对于你的工作肯定大有裨益。我们都知道,当JAVA方法需要传参数时,如两个方法间进行交互,传出值是另一个方法的输入值,随着方法越来越多,我们就容易遇到问题。此时如果利用ThreadLocal,每个线程可以独立保存一个值,非常方便。

下面让我们不再空谈理论,直接开始实际操作吧。让我们先学习一下如何使用这个类。

这里我们不再特意新建一个项目来做这件事情,可以借鉴。我之前关于servlet的文章:Java技术中的经典之作:Servlet的实践运用,我们就在这个项目上做一下二次创作吧。

构建它

首先我们需要构造这样一个类

java 复制代码
package com.masiyi.servlet;

import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import java.io.IOException;

/**
 * @Author masiyi
 * @Date 2023/12/18
 * @PackageName:com.masiyi.servlet
 * @ClassName: MyTheadLocal
 * @Description: TODO
 * @Version 1.0
 */
@WebServlet(urlPatterns = "/myThreadLocal")
public class MyThreadLocal extends HttpServlet {

    ThreadLocal threadLocal = new ThreadLocal<String>();
    /**
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        threadLocal.set("你好");
        method1();
    }

    private void method1() {
        System.out.println(threadLocal.get());
        method2();
    }

    private void method2() {
//        threadLocal.remove();
        System.out.println(threadLocal.get());
    }

    /**
     * @param req
     * @param resp
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(threadLocal.get());
        super.doPost(req, resp);
    }
}

启动后,我们用到接口调用工具,比如apifox,然后我们去调用这个接口。

学习它

跑到这个方法里面以后,我们可以看到我们是用成员属性方法创建了一个成员变量,这个时候我们给这个成员变量设置一个值。这里需要了解的是,每个HTTP请求就是一条单独的线程。在这个线程中,无论我们怎么调方法,它都会在同一个线程中执行,除非我们新建了一个异步线程来完成其他操作。所以,我们可以看到我们现在所在线程的名字是"http-nio-8080-exec-1"@2,977。

上面提到过,这个成员变量只有当前线程可以访问。也就是说,在这个线程中设置了一个值后,其他线程无法获取这个变量,只能由当前线程访问。就像我们现在正在使用的method1方法,我们直接从get方法中获取它设定的值,结果显示的是"你好"。

但是,现在如果我们调用这个servlet的post方法,我们将发现它与我们刚刚的线程不同。

那么,自然地,它直接获取的值就是null。

现在我们再次调用get方法,这时我们会发现它的线程已经改变了,这就是我说的道理。每个请求都会有一个独立的线程。因此,当我们直接调用它的get方法时,就得到了null。 接下来,我们再调用method1方法。会发现我们在doget方法中已经设定的值,这次可以顺利获取了。

最后,在这个方法中我们将其remove掉。remove意味着所有的值都会被清空。所以,我们看到的值就是null。

通过这个类,我们就可以实现了,方法的输入和输出参数不用明确标明它们的具体值,我们可以通过这个类来传递值,而且这个类的成员变量是每个线程专用的,每个线程都是独立的。

利用它

ThreadLocal在工作中可以用来解决线程安全和上下文传递的问题。举个例子,假设我们有一个需要在多个方法中传递用户身份信息的场景,可以使用ThreadLocal来存储用户身份信息,以便在整个线程中访问。

java 复制代码
public class UserContext {
    private static final ThreadLocal<User> userThreadLocal = new ThreadLocal<>();

    public static void setUser(User user) {
        userThreadLocal.set(user);
    }

    public static User getUser() {
        return userThreadLocal.get();
    }

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

public class UserService {
    public void processUserRequest() {
        User user = // 从请求中获取用户信息
        UserContext.setUser(user);
        // 执行业务逻辑
        // 在其他方法中可以通过UserContext.getUser()获取用户信息
        UserContext.clear();
    }
}

在上面的例子中,UserContext类使用ThreadLocal存储用户信息,而UserService类中的processUserRequest方法可以在整个线程中访问和传递用户信息,而不需要显式地将用户信息作为参数传递给每个方法。这样可以简化代码,避免在方法间传递上下文信息的复杂性。

查看它

让我们仔细看看这三个方法,set gat remove,它们各有什么特点,为什么能做到如此独特的操作?

set方法

java 复制代码
  public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map!= null) {
      map.set(this, value);
    } else {
      createMap(t, value);
    }
  }

这是ThreadLocal类中的set方法,用于把特定的值塞进当前线程的ThreadLocalMap里。首先,它拿到了当前线程的引用,然后尝试从这个线程的ThreadLocalMap里捞出对应的map。如果映射存在,就把值藏进去;如果映射不存在,那就造一个新的出来,然后再把值藏进去。总之就是要保证每个线程都有自己专享的ThreadLocalMap,顺利实现了线程局部变量的存储和获取,避免了多线程环境下的数据共享和争抢问题。

具体来说,这段代码里最关键的操作就是通过ThreadLocalMap来实现线程局部变量的存储和访问,同时保证每个线程都能自由处理自己的变量副本。

get方法

java 复制代码
  public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map!= null) {
      ThreadLocalMap.Entry e = map.getEntry(this);
      if (e!= null) {
        @SuppressWarnings("unchecked")
        T result = (T)e.value;
        return result;
      }
    }
    return setInitialValue();
  }

这是ThreadLocal类里的get方法,用于从当前线程的ThreadLocalMap里把保存的值取出来。首先,它拿到当前线程的引用,然后尝试从当前线程的ThreadLocalMap里把对应的map找到。如果映射存在,就尝试从map里把值找出来,如果找到了就直接返回;如果找不到,就调用setInitialValue方法来初始化一个值并回传。

这个方法的作用就是确保每个线程都能取到自己专享的那份ThreadLocalMap里保存的值。如果当前线程的ThreadLocalMap里有对应的值,那就直接取回来;如果没有,那就可以通过setInitialValue方法初始化一个值,然后继续取回来。

remove方法

java 复制代码
 public void remove() {
     ThreadLocalMap m = getMap(Thread.currentThread());
     if (m!= null) {
       m.remove(this);
     }
 }

remove方法主要用于从当前线程的ThreadLocalMap中把跟当前ThreadLocal对象相关联的值给清理掉。首先,它拿到当前线程的引用,然后尝试从当前线程的ThreadLocalMap里找到对应的map。如果map存在,就调用map的remove方法把跟ThreadLocal对象相关联的值给清理掉。

这个方法主要是为了保证在不再需要用ThreadLocal对象保存的值时,能将其从当前线程的ThreadLocalMap中清理掉,避免内存泄露或者无谓的资源占用。

如果你去深入研究源码,就会发现这里面主要涉及三个类,ThreadLocalMap、Entry和ThreadLocal。它们的关系大致如下:

  • ThreadLocalMap是ThreadLocal类的一个子类,主要用来存储线程局部变量的值。每个线程都有自己专属的ThreadLocalMap实例,以此来存储该线程所需的局部变量值。
  • Entry是ThreadLocalMap里面的一个内置类,主要用来表示ThreadLocalMap里的项。每个Entry实例都包括一个ThreadLocal对象和相应的值,用于描绘线程局部变量的键值对。
  • ThreadLocal是一个线程局部变量,它提供了get、set和remove等方法,用于在当前线程里存取局部变量的值。每个ThreadLocal实例都跟当前线程的ThreadLocalMap里面的一个Entry相关联,以此来实现线程局部变量的存储和访问。

于是,ThreadLocalMap主要用来存储线程局部变量的值,Entry是用来描绘存储在ThreadLocalMap里面的键值对,而ThreadLocal就是线程局部变量的一种抽象表示,它跟ThreadLocalMap里面的Entry相绑定,使得线程局部变量的存储和访问得以实现。

相关推荐
weixin_446707743 分钟前
IDEA2024 maven构建跳过测试
java·maven
开朗觉觉10 分钟前
RabbitMQ高可用&&延迟消息&&惰性队列
java·rabbitmq·java-rabbitmq
zmd-zk11 分钟前
flink学习(3)——方法的使用—对流的处理(map,flatMap,filter)
java·大数据·开发语言·学习·flink·tensorflow
昵称202114 分钟前
flink1.16+连接Elasticsearch7官方例子报错解决方案
java·flink·es7
爱编程的小生26 分钟前
Easyexcel(6-单元格合并)
java·excel
2301_8112743128 分钟前
springboot嗨玩旅游网站
spring boot·后端·旅游
小白不太白95035 分钟前
设计模式之 迭代器模式
java·设计模式·迭代器模式
闲人一枚(学习中)37 分钟前
设计模式-创建型-单例模式
java·单例模式·设计模式
Coderfuu1 小时前
Java技术复习提升 10异常
java·开发语言
愿天垂怜1 小时前
【C++】C++11引入的新特性(1)
java·c语言·数据结构·c++·算法·rust·哈希算法