一、ThreadLocal的介绍
- ThreadLocal 是 Java 中的一个类,它提供了线程局部变量的功能。线程局部变量是指每个线程拥有自己独立的变量副本,这些变量在不同的线程中互不影响。ThreadLocal 提供了一种在多线程环境下,每个线程都可以独立访问自己的变量副本的机制。
- ThreadLocal并不是一个线程,而是一个线程的局部变量
- ThreadLocal为每个线程提供单独的一份存储空间,具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问。
二、ThreadLocal的使用场景
在springboot中,客户端每一次发送请求,tomcat服务器都会分配一个单独的线程,然后在这个线程上可能要执行不同的代码,比如controller、拦截器的代码啊,service的代码等等,它们都属于同一个线程。满足这个要求就可以使用ThreadLocal进行存储数据。(每一个线程都有一个单独的存储空间,那么在线程的生命周期内,我们可以共享这份存储空间)
-
ThreadLocal 在 Java 中有许多使用场景,主要用于解决以下两类问题:
-
- 线程封闭性(Thread Confinement):有些对象是非线程安全的,但是它们只在特定的线程中使用,并不被多个线程共享。使用 ThreadLocal 可以确保每个线程都有自己的对象副本,从而避免了线程安全问题。
-
- 避免传递参数(Avoid Passing Parameters):有些情况下,某个对象需要在一个方法调用链中的多个方法之间传递,但这个对象对于整个调用链来说是相同的,不应该在方法之间显式地传递。使用 ThreadLocal 可以将这个对象存储在线程局部变量中,而不必在方法参数中传递。
-
-
一些典型的 ThreadLocal 使用场景包括:
-
数据库连接管理:在多线程环境下,每个线程需要独立的数据库连接,可以使用 ThreadLocal 来管理每个线程的数据库连接,避免多个线程共享连接导致的线程安全问题。
-
会话管理:在 Web 应用中,每个用户会话通常都需要存储用户的身份信息或者其他会话相关的数据,可以使用 ThreadLocal 来存储每个用户会话的信息,确保线程安全性。
-
日志跟踪:在分布式系统中,通常需要在不同的服务之间传递某个请求的唯一标识符(例如请求ID),可以使用 ThreadLocal 来存储这个标识符,从而在整个请求处理过程中都可以方便地访问到它。
-
线程池任务参数传递:在使用线程池执行任务时,有时候需要将一些任务相关的参数传递给执行任务的线程,可以使用 ThreadLocal 来存储这些参数,而不必在任务执行时显式地传递参数。
-
-
总的来说,ThreadLocal 适合于需要在多个方法调用之间共享数据,但又不希望使用方法参数显式传递数据的情况。但需要注意,滥用 ThreadLocal 会导致内存泄漏或者其他问题,因此在使用时需要谨慎。
三、ThreadLocal的实现原理
- ThreadLocal 的实现原理主要涉及 ThreadLocal 类本身以及 Thread 类的实现机制。
-
ThreadLocal 类:
- ThreadLocal 内部维护了一个以当前线程为 key、存储的对象为 value 的 map,这个 map 是 ThreadLocal 的一个静态成员变量,被所有的 ThreadLocal 实例共享。
- 每个 ThreadLocal 实例通过 get()、set() 方法与当前线程关联,通过当前线程获取或设置对应的值。
- ThreadLocal 的 get() 方法首先获取当前线程,然后通过当前线程作为 key 在 map 中查找对应的值。
- ThreadLocal 的 set() 方法首先获取当前线程,然后将当前线程与设置的值关联起来,存储在 map 中。
-
Thread 类:
- Java 中的线程是由 Thread 类实现的。每个线程对象都会包含一个 ThreadLocalMap 类型的成员变量,用于存储线程局部变量。
- 当调用 ThreadLocal 的 set() 方法时,实际上是将值存储到当前线程的 ThreadLocalMap 中,以 ThreadLocal 对象为键,值为值。
- 当调用 ThreadLocal 的 get() 方法时,实际上是从当前线程的 ThreadLocalMap 中获取对应的值。
总的来说,ThreadLocal 的实现原理是通过在每个线程中维护一个 ThreadLocalMap 对象,这个对象中存储了当前线程所有 ThreadLocal 对象的键值对。通过这种方式,每个线程可以独立地访问自己的线程局部变量,而不会与其他线程产生冲突。需要注意的是,由于 ThreadLocalMap 是存储在每个线程中的,因此需要注意内存泄漏的问题,及时清理不再需要的线程局部变量是很重要的。
四、ThreadLocal的基本使用
public class ThreadLocalExample {
// 定义一个 ThreadLocal 变量
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
// 在主线程中设置 ThreadLocal 的值
threadLocal.set("Main Thread Value");
// 创建一个新的线程,并在其中设置 ThreadLocal 的值
Thread thread = new Thread(() -> {
// 在子线程中获取并输出 ThreadLocal 的值
System.out.println("ThreadLocal value in new thread: " + threadLocal.get());
// 设置新的 ThreadLocal 值
threadLocal.set("New Thread Value");
// 再次输出新的 ThreadLocal 值
System.out.println("Updated ThreadLocal value in new thread: " + threadLocal.get());
});
thread.start();
// 主线程中获取并输出 ThreadLocal 的值
System.out.println("ThreadLocal value in main thread: " + threadLocal.get());
// 清除 ThreadLocal 的值
threadLocal.remove();
}
}
输出:
ThreadLocal value in main thread: Main Thread Value
ThreadLocal value in new thread: null
Updated ThreadLocal value in new thread: New Thread Value
- 在这个示例中,主线程通过 threadLocal.set(“Main Thread Value”) 设置了一个 ThreadLocal 的值。
- 然后创建了一个新的线程,在新线程中通过 threadLocal.get() 获取了这个值(
发现获取不到,为null,因为不在同一个线程
),并设置了新的值。 - 在主线程和新线程中分别输出了 ThreadLocal 的值。主线程获取到的是一开始设置的值,而新线程获取到的是新线程里面设置的值。
- 值得注意的是,在主线程和新线程中的 ThreadLocal 值是相互独立的,互不影响。最后通过threadLocal.remove() 清除了 ThreadLocal 的值。
这样就展示了 ThreadLocal 在不同线程中可以存储不同的数据副本,每个线程都可以独立地访问和修改自己的副本,而不会影响其他线程。
五、使用ThreadLocal的时候一般都会封装成一个工具类
public class BaseContext {
public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();
public static void setCurrentId(Long id) {
threadLocal.set(id);
}
public static Long getCurrentId() {
return threadLocal.get();
}
public static void removeCurrentId() {
threadLocal.remove();
}
}