系列博客目录
文章目录
1.为什么我可以在拦截其中BaseContext.setCurrentId(empId);然后在controller中通过BaseContext.getCurrentId(empId);拿到值。
你能够在拦截器中调用 BaseContext.setCurrentId(empId)
,并在控制器中通过 BaseContext.getCurrentId()
拿到值,是因为 ThreadLocal
的作用。ThreadLocal
可以让你在同一个线程内跨方法传递数据,而这些数据是线程独立的。
主要原因是 ThreadLocal
如何工作
1. ThreadLocal
是基于线程的
ThreadLocal
会为每个线程维护一份独立的变量副本。当你在拦截器中调用 BaseContext.setCurrentId(empId)
时,数据会存储在当前请求的线程的 ThreadLocal
存储中。同一请求线程中后续所有的方法调用都可以访问到这份存储。
- 在拦截器中调用
BaseContext.setCurrentId(empId)
,将empId
设置到当前线程的ThreadLocal
存储中。 - 在控制器中调用
BaseContext.getCurrentId()
,可以从当前线程的ThreadLocal
中获取先前存储的数据(即empId
)。
2. 请求线程的独立性
当 Web 应用处理一个 HTTP 请求时,通常每个请求都会在一个独立的线程中执行。因此,所有在同一线程内执行的操作(例如拦截器和控制器中的方法)都可以共享同一个 ThreadLocal
变量的数据。
- 在拦截器中设置
BaseContext.setCurrentId(empId)
时,empId
会被保存在当前请求所使用的线程中的ThreadLocal
变量里。 - 当请求到达控制器时,同一线程仍然在执行,所以控制器可以通过
BaseContext.getCurrentId()
获取到相同的empId
。
工作过程:
-
拦截器阶段:
- 在请求的拦截器中,你调用了
BaseContext.setCurrentId(empId)
,此时empId
被保存在当前请求线程的ThreadLocal
存储中。
public class MyInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Long empId = getEmpIdFromRequest(request); BaseContext.setCurrentId(empId); // 将 empId 设置到当前线程的 ThreadLocal 中 return true; } }
- 在请求的拦截器中,你调用了
-
控制器阶段:
- 在控制器中,通过
BaseContext.getCurrentId()
获取当前线程的ThreadLocal
中的empId
,因为该线程中存储了拦截器中设置的数据。
@RestController public class MyController { @GetMapping("/getEmpId") public Long getEmpId() { return BaseContext.getCurrentId(); // 从当前线程的 ThreadLocal 中获取 empId } }
- 在控制器中,通过
关键点:
- 线程隔离:
ThreadLocal
保证每个线程有独立的存储,因此,在同一线程中跨方法调用时,能够共享ThreadLocal
存储的数据。 - 拦截器和控制器共享同一线程:虽然拦截器和控制器是不同的组件,但它们处理同一个 HTTP 请求时,通常是由同一个线程来执行的,因此它们都能访问到同一个
ThreadLocal
存储的数据。
举个例子:
假设你在请求的拦截器中设置了 empId
,然后在控制器中获取它。
拦截器代码:
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 从请求中获取 empId
Long empId = 123L;
// 设置到当前线程的 ThreadLocal 中
BaseContext.setCurrentId(empId);
return true; // 放行请求,继续执行后续处理
}
}
控制器代码:
@RestController
public class MyController {
@GetMapping("/getEmpId")
public Long getEmpId() {
// 获取当前线程的 ThreadLocal 中存储的 empId
return BaseContext.getCurrentId();
}
}
过程:
- 请求到达拦截器:拦截器会通过
BaseContext.setCurrentId(empId)
将empId
存储在当前线程的ThreadLocal
中。 - 请求到达控制器:控制器中的
BaseContext.getCurrentId()
会从当前线程的ThreadLocal
中获取到存储的empId
。
总结:
- 你在拦截器中通过
BaseContext.setCurrentId(empId)
存储了数据到当前线程的ThreadLocal
中。 - 在控制器中,通过
BaseContext.getCurrentId()
获取同一个线程中的ThreadLocal
数据,从而拿到empId
。 ThreadLocal
确保了每个线程有独立的存储空间,因此在同一线程中的不同方法之间,可以共享数据。
2.为什么两次BaseContext不是产生两个独立的对象
在 Java 中,BaseContext.setCurrentId(empId)
和 BaseContext.getCurrentId()
调用并不会产生两个独立的对象,而是使用了同一个 ThreadLocal
对象。这是因为 ThreadLocal
的设计本身就是为了在同一个线程内共享数据。我们来逐步解析这个问题。
为什么不会产生两个独立的对象?
-
ThreadLocal
是单一对象:
BaseContext
中定义的ThreadLocal
变量threadLocal
是类级别的静态变量,它只有一个实例,所有调用setCurrentId
和getCurrentId
方法的代码都会访问同一个ThreadLocal
对象。public class BaseContext { public static ThreadLocal<Long> threadLocal = new ThreadLocal<>(); public static void setCurrentId(Long id) { threadLocal.set(id); // 设置当前线程的 threadLocal 数据 } public static Long getCurrentId() { return threadLocal.get(); // 获取当前线程的 threadLocal 数据 } public static void removeCurrentId() { threadLocal.remove(); // 清除当前线程的 threadLocal 数据 } }
- 这个
threadLocal
是一个静态成员变量,意味着它是 属于类的,而不是实例化的对象。它在类加载时创建,并且在整个应用生命周期内只有一个实例。 - 所以无论调用多少次
setCurrentId
或getCurrentId
方法,都在操作同一个ThreadLocal
对象。
- 这个
-
ThreadLocal
的作用域是线程级别的:
ThreadLocal
是按线程隔离的,每个线程都有自己独立的存储空间,因此它允许在同一线程中的不同方法之间共享数据。但 它不会为每个方法或每次调用创建新的ThreadLocal
对象。- 在你的代码中,调用
BaseContext.setCurrentId(empId)
和BaseContext.getCurrentId()
实际上都在访问 同一个ThreadLocal
,只是操作的是当前线程内部的独立存储(由ThreadLocal
管理)。 - 也就是说,虽然调用了
set
和get
,它们是在 同一个线程内操作ThreadLocal
中的数据,不会在每次调用时创建新的ThreadLocal
对象。
- 在你的代码中,调用
-
ThreadLocal
的设计是线程隔离:
ThreadLocal
是为每个线程提供独立存储的。它会为每个线程维护一个独立的副本,但在 同一个线程内,所有对该线程的ThreadLocal
变量的访问都是共享的。- 当你在一个线程中调用
BaseContext.setCurrentId(empId)
时,empId
会被保存在该线程的ThreadLocal
存储中。 - 当你在同一个线程中调用
BaseContext.getCurrentId()
时,你拿到的就是该线程的ThreadLocal
存储中的值。
- 当你在一个线程中调用
重要的概念:
-
线程隔离:
每个线程有一个独立的ThreadLocal
存储区域。这意味着,同一个ThreadLocal
对象不会在不同线程之间共享数据,而是在每个线程内保持独立性。 -
单一的
ThreadLocal
对象:
在类中定义的ThreadLocal
是单一的,它只会有一个实例。在同一个线程中,它允许不同方法之间共享数据,因为这些方法访问的是同一个ThreadLocal
存储区域。
示例:
假设你有以下代码:
public class MyClass {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void setCurrentValue(String value) {
threadLocal.set(value);
}
public static String getCurrentValue() {
return threadLocal.get();
}
}
在同一个线程中,调用 setCurrentValue
和 getCurrentValue
会操作同一个 ThreadLocal
存储。
示例调用:
public class Test {
public static void main(String[] args) {
MyClass.setCurrentValue("Hello, World!");
String value = MyClass.getCurrentValue();
System.out.println(value); // 输出: Hello, World!
}
}
运行过程:
- 当调用
MyClass.setCurrentValue("Hello, World!")
时,"Hello, World!"
被保存在当前线程的ThreadLocal
存储中。 - 当调用
MyClass.getCurrentValue()
时,它会从当前线程的ThreadLocal
存储中获取到Hello, World!
。
为什么不会生成多个 ThreadLocal
实例?
- 因为
threadLocal
变量是 静态的,它只会在类加载时创建一个实例,不会根据方法调用、线程调用等多次生成新实例。 - 不同方法之间并不会重新创建
ThreadLocal
对象,它们操作的是同一个ThreadLocal
变量。
总结:
BaseContext.setCurrentId
和BaseContext.getCurrentId
操作的是同一个ThreadLocal
变量,且ThreadLocal
在类中只有一个实例。ThreadLocal
是基于线程的,它在同一个线程内允许方法之间共享数据,但不会在每次调用时创建新的对象。