ThreadLocal
方法
package com.example.a;
public class Demo {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
private static ThreadLocal<String> threadLocal2 = new ThreadLocal<>();
static void print(String str){
System.out.println(str + ":" + threadLocal.get());
System.out.println(str + ":" + threadLocal2.get());
}
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set("abc");
threadLocal2.set("abc2");
print("thread1 variable");
// 必须要清除,否则可能导致内存泄露
threadLocal.remove();
threadLocal2.remove();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set("def");
threadLocal2.set("def2");
print("thread2 variable");
// 必须要清除,否则可能导致内存泄露
threadLocal.remove();
threadLocal2.remove();
}
});
thread1.start();
thread2.start();
}
}
介绍如何使用ThreadLocal保存每次请求的用户信息。
保存之后,后边的业务操作就可以获取(Controller或者Service都可以,只要在一个请求中)。
代码
用户实体类
package com.knife.common.entity;
import lombok.Data;
@Data
public class UserDTO {
private Long userId;
private String userName;
}
定义ThreadLocal
package com.knife.common.util;
import com.knife.common.entity.UserDTO;
public class UserThreadLocal {
/**
* 构造函数私有
*/
private UserThreadLocal() {
}
private static final ThreadLocal<UserDTO> USER_INFO_THREAD_LOCAL =
new ThreadLocal<>();
/**
* 清除用户信息
*/
public static void clear() {
USER_INFO_THREAD_LOCAL.remove();
}
/**
* 存储用户信息
*/
public static void write(UserDTO userDTO) {
USER_INFO_THREAD_LOCAL.set(userDTO);
}
/**
* 获取当前用户信息
*/
public static UserDTO read() {
return USER_INFO_THREAD_LOCAL.get();
}
}
保存属性
可以放到以下任意一个地方:
过滤器
拦截器
ControllerAdvice
AOP
本处我使用拦截器。
拦截器类
package com.knife.common.interceptor;
import com.knife.common.entity.UserDTO;
import com.knife.common.util.UserThreadLocal;
import lombok.NonNull;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class UserInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull Object handler) {
// 此处实际应该根据header的token解析出用户
// 本处为了简单,直接虚构一个用户
UserDTO userDTO = new UserDTO();
userDTO.setUserId(3L);
userDTO.setUserName("Tony");
UserThreadLocal.write(userDTO);
return true;
}
@Override
public void afterCompletion(@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull Object handler,
Exception e) {
UserThreadLocal.clear();
}
}
注册拦截器
package com.knife.common.config;
import com.knife.common.interceptor.UserInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new UserInterceptor());
}
}
获取属性
package com.knife.controller;
import com.knife.common.entity.UserDTO;
import com.knife.common.util.UserThreadLocal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
@RestController
public class HelloController {
@GetMapping("/test")
public String test() {
UserDTO currentUser = UserThreadLocal.read();
System.out.println(currentUser.toString());
return "test success";
}
}
测试
原理总结(图文)
流程追踪
set()流程
threadLocal.set("abc"); // ThreadLocal#set
//传进来的参数名为value
//获取当前线程
Thread t = Thread.currentThread();
//获得此线程的ThreadLocalMap变量。(若为null则创建它,若有则直接使用)
// ThreadLocalMap是ThreadLocal的静态内部类。即:ThreadLocal.ThreadLocalMap
// ThreadLocalMap内部是弱引用(WeakReference)
ThreadLocalMap map = getMap(t); // ThreadLocal#getMap
return t.threadLocals;
//保存数据。key为当前ThreadLocal对象,value为传进来的值
map.set(this, value)
get()流程
threadLocal.get() // ThreadLocal#get
//获取当前线程
Thread t = Thread.currentThread();
//获得此线程的ThreadLocalMap变量
ThreadLocalMap map = getMap(t);
//获得当前线程的以当前ThreadLocal为key的值
ThreadLocalMap.Entry e = map.getEntry(this);
//强制转换为原来的类型并返回
T result = (T)e.value;
return result;
ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key是 ThreadLocal 实例本身,value 是真正需要存储的 Object。
也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。图中的虚线表示 ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key的,弱引用的对象在 GC 时会被回收。