ThreadLocal 解析

ThreadLocal 类用来提供线程内部的局部变量,类似于方法中的局部变量。这种变量在多线程环境下访问(通过 get 或 set 方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。ThreadLocal 实例通常来说都是 private static 类型的,用于关联线程和线程的上下文。

ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。

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 supplier) {
    return new SuppliedThreadLocal<>(supplier);
}

static final class SuppliedThreadLocal extends ThreadLocal {
                                                                  
    private final Supplier supplier;                 
                                                                  
    SuppliedThreadLocal(Supplier 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();                             
}                                                         
  1. 首先获取当前线程
  2. 根据当前线程获取一个 ThreadLocalMap
  3. 如果获取的 ThreadLocalMap 不为空,则在 ThreadLocalMap 中以 ThreadLocal 的引用作为 key 来在 ThreadLocalMap 中获取对应的值,否则转到 5
  4. 如果 e 不为 null,则返回 e.value,否则转到 5
  5. map 为空或者 e 为空,则通过 initialValue 函数获取初始值 value,然后用ThreadLocal 的引用和 该value 作为 Key/Value 创建一个新的 ThreadLocalMap

Thread 中 保存有该 ThreadLocalMap

ThreadLocal.ThreadLocalMap threadLocals = null;

每一个 Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal 实例的引用,value 是真正需要存储的 Object。

ThreadLocalMap 解析

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

ThreadLocal 使用

模拟记录方法调用的时间

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();
    }
}

SpringMVC 中的应用:
ThreadLocal 解析_第1张图片

ThreadLocal 解析_第2张图片

你可能感兴趣的:(JAVA)