ThreadLocal 类用来提供线程内部的局部变量,类似于方法中的局部变量。这种变量在多线程环境下访问(通过 get 或 set 方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。ThreadLocal 实例通常来说都是 private static 类型的,用于关联线程和线程的上下文。
ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
如何构造 ThreadLocal 实例
方法一:
@Test
public void test3() {
ThreadLocal threadLocal = new ThreadLocal(){
@Override
protected Long initialValue() {
return System.currentTimeMillis();
}
};
}
方法二:JDK 1.8 引入的函数式接口
@FunctionalInterface
public interface Supplier {
T get();
}
public static ThreadLocal withInitial(Supplier extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
static final class SuppliedThreadLocal extends ThreadLocal {
private final Supplier extends T> supplier;
SuppliedThreadLocal(Supplier extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
@Override
protected T initialValue() {
return supplier.get();
}
}
ThreadLocal threadLocal = ThreadLocal.withInitial(System::currentTimeMillis);
System.out.println(threadLocal.get());
protected T initialValue() {
return null;
}
返回当前线程的 ThreadLocal 的“初始值”。
这个方法将在一个线程第一次使用get方法访问变量时调用,除非线程先前调用了set方法,在这种情况下,不会为线程调用initialValue方法。
通常情况下,每个线程最多调用一次此方法,但在get()后调用remove(),可能会再次调用此方法。
这个实现只是返回null; 如果程序员希望线程局部变量具有非null的初始值,则必须对ThreadLocal进行子类化,并重写此方法。通常,将使用匿名内部类。
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();
}
Thread 中 保存有该 ThreadLocalMap
ThreadLocal.ThreadLocalMap threadLocals = null;
每一个 Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal 实例的引用,value 是真正需要存储的 Object。
https://zhuanlan.zhihu.com/p/34406557
https://www.jianshu.com/p/dde92ec37bd1
https://www.jianshu.com/p/30ee77732843
https://blog.csdn.net/z_chenchen/article/details/88915901
https://zhuanlan.zhihu.com/p/40515974
模拟记录方法调用的时间
class Profiler {
private static final ThreadLocal TIME_THREAD_LOCAL = ThreadLocal.withInitial(System::currentTimeMillis);
public static void begin() {
TIME_THREAD_LOCAL.set(System.currentTimeMillis());
}
public static long end() {
return System.currentTimeMillis() - TIME_THREAD_LOCAL.get();
}
}
public class T {
@Test
public void test() {
Profiler.begin();
try {
TimeUnit.MILLISECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Profiler.end());
}
}
比如在一个 web 应用中,我需要保存登陆用户的信息。就可以保存在 ThreadLocal 中。
public class UserTokenThreadLocal {
private static ThreadLocal contents = new ThreadLocal<>();
public static void set(UserLoginModel userLoginModel) {
contents.set(userLoginModel);
}
public static UserLoginModel get() {
return contents.get();
}
public static void clear() {
contents.remove();
}
}
ThreadLocal 与 SpringMVC 的拦截器配合的天衣无缝。
比如,在 Controller 中的一些方法需要用户登陆才能执行。
在 Controller 处理前,将用户登陆的信息存放在 ThreadLocal 中。每次次请求/响应对应一个线程,所以与每个线程绑定的 ThreadLocal 也是唯一的,其他的线程无法访问。
public class UserTokenAuthInterceptor implements HandlerInterceptorAdapter {
private final UserInfoService userInfoService;
@Autowired
public UserTokenAuthInterceptor(UserInfoService userInfoService) {
this.userInfoService = userInfoService;
}
@Override
public boolean preHandle(HttpServletRequest req, HttpServletResponse res, Object handler) throws Exception {
try {
String token = req.getHeader("token");
if (StringUtils.isBlank(token)) {
token = req.getParameter("token");
if (StringUtils.isBlank(token)) {
return write(res, false);
}
}
UserModel userModel = userInfoService.getUserInfoByToken(token);
if (userModel == null || userModel.getId() == 0) {
return write(res, false);
}
UserTokenThreadLocal.set(new UserLoginModel(userModel.getId(), token, userModel.getMobile(), userModel.getUsername(), userModel.getAvatarUrl()));
return true;
} catch (Exception e) {
//
}
return write(res, false);
}
private boolean write(HttpServletResponse res, boolean apiLogin) throws IOException {
if (!apiLogin) {
res.setContentType("application/json;charset=UTF-8");
Writer writer = res.getWriter();
writer.write(JSON.toJSONString(ApiResult.newResult(ServerCode.NO_LOGIN)));
}
return apiLogin;
}
@Override
public void afterCompletion(HttpServletRequest req, HttpServletResponse res, Object arg2, Exception arg3) throws Exception {
UserTokenThreadLocal.clear();
}
}