private final ThreadLocal<StudentEntity> studentEntityThreadLocal = new NamedThreadLocal<>("studentInfo");
public void verify(String name) {
if (StringUtils.isBlank(name)) {
studentEntityThreadLocal.remove();
}
}
public void verify2(StudentEntity student) {
try {
studentEntityThreadLocal.set(student);
} finally {
studentEntityThreadLocal.remove();
}
}
以上代码中,在执行studentEntityThreadLocal.remove();前,verify 方法中未使用 studentEntityThreadLocal 的set方法,verify2中使用了set方法。为什么会有这2中用法?verify 方法中没有set不会报错吗?
在分析这两个方法前,我们需要理解 ThreadLocal
的核心特性:它为每个线程提供独立的变量副本,变量的生命周期与线程绑定。ThreadLocal
的 get()
方法会返回当前线程的变量副本,如果该线程尚未调用 set()
,则返回 null
(或初始值,取决于是否重写了 initialValue()
方法)。
这两种用法分别代表了 ThreadLocal
的两种典型使用模式:
ThreadLocal
被用作全局上下文容器ThreadLocal
被用作方法内部的临时状态存储try-finally
确保无论执行是否成功都清除状态ThreadLocal
的设计允许在未调用 set()
之前直接调用 get()
或 remove()
,这是因为:
get()
方法会返回 null
(如果没有初始值)remove()
方法会移除当前线程的变量副本,如果不存在则不执行任何操作import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@RestController
public class StudentController {
// 创建一个命名的ThreadLocal,用于存储当前线程的投组单元信息
private static final ThreadLocal<StudentEntity> studentEntityThreadLocal =
new NamedThreadLocal<>("PortfolioUnitInfo");
// 模拟数据库
private StudentRepository studentRepository = new StudentRepository();
// 处理学生信息查询的请求
@GetMapping("/student")
public String getStudentInfo(@RequestParam String studentId, HttpServletRequest request) {
try {
// 从数据库查询学生信息
StudentEntity student = studentRepository.findById(studentId);
// 将学生信息存储到ThreadLocal中
studentEntityThreadLocal.set(student);
// 执行一些需要访问学生信息的操作
String result = processStudentInfo();
// 也可以在其他方法中访问ThreadLocal中的数据
logRequestInfo(request);
return result;
} finally {
// 清理ThreadLocal,避免内存泄漏
studentEntityThreadLocal.remove();
}
}
// 处理学生信息的方法,不需要显式传递学生对象
private String processStudentInfo() {
StudentEntity student = studentEntityThreadLocal.get();
if (student != null) {
return "学生信息: " + student.getName() + ", 年龄: " + student.getAge();
}
return "未找到学生信息";
}
// 验证方法,根据条件清除ThreadLocal
public void verify(String name) {
if (StringUtils.isBlank(name)) {
studentEntityThreadLocal.remove();
}
}
// 验证方法2,临时设置ThreadLocal并确保清理
public void verify2(StudentEntity student) {
try {
studentEntityThreadLocal.set(student);
// 执行一些需要访问学生信息的操作
System.out.println("验证学生: " + student.getName());
} finally {
studentEntityThreadLocal.remove();
}
}
// 记录请求信息,同时包含学生信息
private void logRequestInfo(HttpServletRequest request) {
StudentEntity student = studentEntityThreadLocal.get();
String studentName = student != null ? student.getName() : "未登录";
System.out.println("请求路径: " + request.getRequestURI() + ", 当前学生: " + studentName);
}
// 演示线程池中的使用
public void demonstrateThreadPool() {
ExecutorService executor = Executors.newFixedThreadPool(2);
for (int i = 0; i < 5; i++) {
final int index = i;
executor.submit(() -> {
try {
// 在线程池中设置ThreadLocal
studentEntityThreadLocal.set(new StudentEntity("学生" + index, 20 + index));
// 执行任务
System.out.println(Thread.currentThread().getName() + ": " +
studentEntityThreadLocal.get().getName());
} finally {
// 重要:在线程池环境中必须清理ThreadLocal
studentEntityThreadLocal.remove();
}
});
}
executor.shutdown();
}
// 学生实体类
static class StudentEntity {
private String name;
private int age;
public StudentEntity(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
// 模拟学生数据访问层
static class StudentRepository {
public StudentEntity findById(String id) {
// 实际应用中会从数据库查询
return new StudentEntity("张三", 20);
}
}
}
典型场景
:Web 应用中的用户认证
优点:减少参数传递,提高代码可读性
风险:需要确保在请求结束时清除状态,否则在线程池环境中会导致内存泄漏或数据错乱
典型场景
:批量数据处理
优点:线程安全,不会影响其他线程或任务
最佳实践:始终在 try-finally
块中使用,确保状态被正确清理
这两种用法都是合理且必要的,它们分别适用于不同的场景:
在实际应用中,通常会结合使用这两种模式,构建完整的线程上下文管理机制。