> 文档中心 > ThreadLocal详解-ThreadLocal这一篇就够了

ThreadLocal详解-ThreadLocal这一篇就够了


ThreadLocal简介

ThreadLocal:用于存储当前线程变量,对其他线程是隔离,ThreadLocal为每个线程提供 get() 或 set() 方法来创建独立初始化的变量副本;

ThreadLocal实例通常是类中希望将状态与线程相关联的私有静态字段(例如,用户ID 或 事务ID);只要线程处于活动状态且ThreadLocal实例是可访问的,每个线程都持有对其线程局部变量副本的隐式引用;在线程消失后,它的所有线程本地实例副本都将受到垃圾回收(除非存在对这些副本的其他引用)

使用场景

    1.线程间的数据隔离
    2.进行事务操作,存储线程事务信息
    3.数据库连接、Session会话管理
    4.在进行对象跨层传递时,打破层次间的约束

Thread源码说明

     ThreadLocal是一个泛型类,通过泛型可以指定要存储的类型,源码中只提供了一个构造方法,这个构造方法通常是单独使用的,也可以配合 initialValue() 方法使用,重写该方法是在ThreadLocal实例化时提供一个初始值

//方式一:实例化 ThreadLocal 并设置一个初始值ThreadLocal threadId = new ThreadLocal() {    @Override    protected Integer initialValue() { return 1;    }};//方式二:相比方式一ThreadLocal提供了一个静态方法 withInitial 代码看起来更简洁,更优雅ThreadLocal threadId = ThreadLocal.withInitial(() -> 1);//查看源码:Supplier接口提供了一个get方法并且标记为@FunctionalInterface 支持Lambda表达式写法public static  ThreadLocal withInitial(Supplier supplier) {    return new SuppliedThreadLocal(supplier);}//查看源码:SuppliedThreadLocal是ThreadLocal中finnal修饰的静态内部类,继承了ThreadLocal并且重写了initialValue()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();    }}

set方法

   设置当前线程变量值

//设置当前线程局部变量public void set(T value) {    //当前线程实例    Thread t = Thread.currentThread();    //拿到当前线程中的ThreadLocalMap    ThreadLocal.ThreadLocalMap map = getMap(t);    if (map != null)//存在则覆盖 map.set(this, value);    else //创建一个ThreadLocalMap,将当前线程t作为Map的key对应value存储到ThreadLocalMap中 createMap(t, value);}/** * * 拿到当前线程中的ThreadLocalMap * * @param  t 当前线程 * @return the map 存储当前线程局部变量副本 */ThreadLocal.ThreadLocalMap getMap(Thread t) {    return t.threadLocals;}/** * 作用是给传递的线程创建一个对应的 ThreadLocalMap 并把值存进去, * 可以看到新创建的 ThreadLocalMap 被赋值给了线程中的 threadLocals 变量, * 这也说明对应的数据都是存储在各个线程中的,所以每个线程对数据的操作自然不会影响其它线程的数据 * * @param t 当前线程 * @param firstValue map中的初始值 */void createMap(Thread t, T firstValue) {    t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);}//ThreadLocalMap 是一个定制的哈希映射,仅适用于维护当前线程本地值。感兴趣的同学可以看一下源码是怎么实现存储当前线程局部变量的static class ThreadLocalMap {    static class Entry extends WeakReference<ThreadLocal> { /**  * 与此ThreadLocal关联的值  */ Object value; Entry(ThreadLocal k, Object v) {     super(k);     value = v; }    }}

get方法

    

     获取ThreadLocal变量中的值

/** * 返回当前线程本地值(局部变量的值),如果当前线程没有本地值,则调用初始化 initialValue() 方法返回的值 * * @return 当前线程的本地值 */public T get() {    //t:当前线程    Thread t = Thread.currentThread();    //map:当前线程局部变量副本    ThreadLocal.ThreadLocalMap map = getMap(t);    if (map != null) { //this:代表当前ThreadLocal对象,拿到ThreadLocal对应的值 ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) {     @SuppressWarnings("unchecked")     T result = (T)e.value;//ThreadLocal对应的值     return result; }    }    return setInitialValue();//调用setInitialValue方法返回初始值}/** * set() 的另一种实现方式,用于建立初始值。如果用户重写了 set() 方法,则使用它代替 set()。 * * @return 返回初始值 */private T setInitialValue() {    T value = initialValue();//如果是重写了initialValue() 方法这里也可以拿到初始值    Thread t = Thread.currentThread();//当前线程    ThreadLocal.ThreadLocalMap map = getMap(t);    if (map != null) map.set(this, value);    else createMap(t, value);    return value;}

remove方法 

   删除当前线程的局部变量值,当这个线程局部变量随后被当前线程get读取,它的值将通过调用它的 initialValue() 方法重新初始化,除非它的值被当前线程在set时,这可能会导致在当前线程中多次调用 initialValue() 方法。

public void remove() {    ThreadLocal.ThreadLocalMap m = getMap(Thread.currentThread());    if (m != null) m.remove(this);}private void remove(ThreadLocal key) {    ThreadLocal.ThreadLocalMap.Entry[] tab = table;    int len = tab.length;    int i = key.threadLocalHashCode & (len-1);    for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];  e != null;  e = tab[i = nextIndex(i, len)]) { if (e.get() == key) {     e.clear();     expungeStaleEntry(i);     return; }    }}//clear() GC特殊处理private T referent;  /* Treated specially by GC */public void clear() {    this.referent = null;}

ThreadLocal应用场景

   当UserController 类中调用了 userService.getUserInfo(Integer uid) 方法

    此方法是用来根据用户ID获取用户信息的,此时业务需求发生了变更,

   需要你根据前端传入的字符串token来查询用户信息,

     但是你发现这个方法在你的项目中已经被多个地方调用了,此时去更改该方法的入参结构或者是重写一个根据token来查询就会导致牵一发动全身之前引用的地方也会随之更改,有没有一种方式能够减少受影响的范围,同时满足uid和token这两种方式来查询呢?

UserController 中的get方法用来模拟查询用户信息import org.springframework.beans.factory.annotation.Autowired;import org.springframework.http.MediaType;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestControllerpublic class UserController {    @Autowired    private IUserService userService;    @GetMapping(path = "/user/get", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)    public UserInfoDTO get(Integer uid) { return userService.getUserInfo(uid);    }}UserServiceImpl 实现了 IUserService 接口,完成测试数据初始化,getUserInfo实现了根据uid和token获取用户信息import lombok.extern.slf4j.Slf4j;import org.springframework.stereotype.Service;import java.util.ArrayList;import java.util.List;import java.util.Optional;@Slf4j@Servicepublic class UserServiceImpl implements IUserService {    static ThreadLocal tokenStore = new ThreadLocal();    /**     * 临时数据,模拟存储在数据库表的数据     */    private List data = new ArrayList();    public UserServiceImpl() { initData();    }    @Override    public UserInfoDTO getUserInfo(Integer uid) { if (null != uid) {     //模拟根据Id查询用户信息     Optional optionalByUid = data.stream().filter(u -> u.getUid().equals(uid)).findFirst();     if (optionalByUid.isPresent()) {  log.info("根据用户ID {} 查到了信息", uid);  return optionalByUid.get();     } } //模拟从ThreadLocal中拿到用户的token String token = tokenStore.get(); log.info("UserServiceImpl 线程:{} 获取了token:{}", Thread.currentThread().getName(), token); if (null != token) {     //模拟根据token查询用户信息     Optional optionalByToken = data.stream().filter(u -> u.getToken().equals(token)).findFirst();     if (optionalByToken.isPresent()) {  return optionalByToken.get();     } } //用户信息不存在 return null;    }    /**     * 生产临时数据     */    private void initData() { this.data = new ArrayList(); this.data.add(new UserInfoDTO(1, "zhangsan", "Java", "82324242424")); this.data.add(new UserInfoDTO(2, "毛按摩", "python", "623443")); this.data.add(new UserInfoDTO(3, "liuche", "erlang", "3355")); log.info("数据初始化完成");    }}UserFilter 用户过滤器用来获取前端放在Header中的token,在UserFilter中将token放入UserServiceImpl定义的(ThreadLocal) tokenStore中完成跨层级传递对象import lombok.extern.slf4j.Slf4j;import org.apache.commons.lang.StringUtils;import org.springframework.stereotype.Component;import javax.servlet.*;import javax.servlet.annotation.WebFilter;import javax.servlet.http.HttpServletRequest;import java.io.IOException;@Slf4j@WebFilter@Componentpublic class UserFilter implements Filter {    @Override    public void init(FilterConfig filterConfig) throws ServletException {    }    @Override    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; log.info("进入UserFilter..."); //获取前端传的token String token = httpServletRequest.getHeader("Token"); if (StringUtils.isNotBlank(token)) {     //存储到ThreadLocal中     UserServiceImpl.tokenStore.set(token);     log.info("UserFilter 线程:{} 存储了token:{}", Thread.currentThread().getName(), token); } chain.doFilter(request, response);    }    @Override    public void destroy() {    }}