【线程专题】ThreadLocal基础、ThreadLocal应用案例、ThreadLocal设计模式、ThreadLocal源码

ThrealLocal的通俗含义:将数据存到线程中的本地变量中(数据与线程绑定),且通过静态方法拿到当前线程从而拿到本地变量,然后拿到数据,实现了线程隔离;相当于每个线程拥有自己独立的数据空间,可以在里面存取数据,利用这个机制可以实现跨组件的通信,单个线程内的数据共享(不是多线程数据共享)
在这里插入图片描述
中间是本地变量,每个线程访问这个变量都会仅仅访问到自己线程内的ThreadLocalMap
ThrealLocal的应用简述:比如一次请求中,数据可以存在这个线程区域里,然后在任意地方把他拿出来(常见的用法,比如shiro利用静态方法拿到了绑定好用户信息的数据)
Ps:注意线程中存数据,是利用本地变量ThreadLocal做连接的,所以如果声明了不同的本地变量,就可以存不同类型的数据;
ThrealLocal的基本使用

    /**
     * 声明一个本地变量,一般来讲不同的ThreadLocal对象,绑定的数据的意义不同
     * 泛型是为了取出的时候不用强装
     * 一般来讲是静态的,这样调用方便
     */
    private static final ThreadLocal<UserEntity> USER_INFO = new ThreadLocal<>();
    /**
     * 基本API使用
     */
    public static void main(String[] args) {
        USER_INFO.set(new UserEntity(){{//线程绑定数据
            setSex("M");
            setUserName("GodSchool");
        }});
        USER_INFO.get();//取出和线程绑定的数据
        USER_INFO.remove();//删除和线程绑定的数据
    }
Ps:线程隔离的概念:只要线程绑定了数据,无论代码运行中哪个地方拿出来的都是自己线程的数据,线程隔离

ThrealLocal的探索起源:记得当初学习ThreadLocal的时候,仅仅只知道这个静态类可以实现线程安全,但对实际应用完全不知道什么场景下会用到这个技术;后来在一次偶然的调试中,在业务开发中我们经常遇到利用shiro的工具类拿到自己的session,然后拿到用户信息;我产生了一个疑问,session不是应该利用sessionId才能拿出自己的session吗,我没有传sessionId进去,这是知道拿到的session一定是自己的session呢?

后来我进入shiro的源码一看,最后看到了这个会话session是通过ThreadLocal拿出来的,这一样以来我就完全了解了ThreadLocal的作用了,首先shiro会有一个拦截器取出当前request的sessionId,然后利用这个sessionId拿到原本存在内存中的session(再在了MemorySessionDAO里面有个map,用sessionId作为key取出),然后再将这个session,利用ThreadLocal的set方法将这个session跟线程绑定,意味着在线程中调用ThreadLocal的get方法就可以拿出这个session,很显然ThreadLocal的方法是静态的,他能首先能识别当前线程是哪个线程,然后再在线程的对应区域中创建数据也可以从对应区域中取出数据,这就是ThreadLocal的作用,当然这只是通俗的描述,如果想了解ThreadLocal详细的原理和标准分析可以看其他博客,本文只根据应用场景做展示,原理只是略过;

ThrealLocal的应用模仿

依据这个思想,写一个拦截器取出request的数据,然后根据标识id取出session,我们也可以这样做,我的这个案例是来源于我们是springcloud微服务架构,所以业务层没有引入shiro,我想实现在业务层调用一个静态方法可以拿到用户数据;

【1】先封装两个个实体类

ContextProvider(用于组织数据——————将用户信息,用户的request实体,response实体用一个对象统一存起来)
@Getter
class ContextProvider {
    private HttpServletRequest request;
    private HttpServletResponse response;
    private UserEntity userEntity;
    ContextProvider(HttpServletRequest request, HttpServletResponse response,UserEntity userEntity) {
        this.request = request;
        this.response = response;
        this.userEntity=userEntity;
    }
}
LocalProvider (用于封装操作————封装了本地变量的操作方法)
public class LocalProvider {
    /**
     * @原生的ThreadLocal
     * 泛型可以标记数据类型,取出的时候就不用强转了
     */
    private static final ThreadLocal<ContextProvider> USER_INFO = new ThreadLocal<>();
    /**
     * @UserInfo相关方法
     */
    public static UserEntity getUserInfo() {
        ContextProvider contextProvider = (ContextProvider) USER_INFO.get();
        if (contextProvider == null) {
            return null;
        }
        return contextProvider.getUserEntity();
    }
    /**
     * @生命周期相关方法
     */
    public static void init(HttpServletRequest request, HttpServletResponse response, UserEntity userEntity) {
        USER_INFO.set(new ContextProvider(request, response, userEntity));
    }
    public static void destroy() {
        USER_INFO.remove();
    }
    /**
     * @Servlet相关方法
     */
    public static HttpServletRequest getRequest() {
        ContextProvider contextProvider = (ContextProvider) USER_INFO.get();
        return contextProvider.getRequest();
    }

    public static HttpServletResponse getResponse() {
        ContextProvider contextProvider = (ContextProvider) USER_INFO.get();
        return contextProvider.getResponse();
    }
}

【2】写一个拦截器(应用关键)

@Configuration
public class InterceptorConfig extends WebMvcConfigurerAdapter {
    public void addInterceptors(InterceptorRegistry registry) {
        /**
         * @封装会话数据到ThreadLocal
         */
        registry.addInterceptor(new HandlerInterceptor() {
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                  String token=request.getHeader("sessionId");
                  UserEntity userInfo=JwtUtil.parseToken(token);
                  LocalProvider.init(request,response,userInfo);
                return true;
            }
            public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
                    throws Exception {
                     /**
                      * @每次用完就清空
                       */
                LocalProvider.destroy();
            }
        }).addPathPatterns("/**")
        .excludePathPatterns("/admin/user/queryUser/**");
    }
}

【3】取出数据
依据我们的想法,数据已经和线程绑定存在某个区域,接下来我们就可以用静态方法取出来;

           public IPage<InvoiceReimbVo> queryInvoice(InvoicePo po) {
            		UserEntity userInfo = LocalProvider.getUserInfo();
           }

以上代码就是对应我们写的LocalProvider的这个方法,实际就是替代我们操作ThreadLocal的外包装饰,因为这样子写代码就更明确一点,java原生的东西实现的更细更底层更抽象,但是应用上我们可以根据场景将他语义化更明确;

   public static UserEntity getUserInfo() {
        ContextProvider contextProvider = (ContextProvider) USER_INFO.get();
        if (contextProvider == null) {
            return null;
        }
        return contextProvider.getUserEntity();
    }

ThrealLocal的其他应用(跨组件通信)

对于框架来讲,当你的封装越来越多的时候,显然你想要传一个数据到某一个对象里,这是很难的,难道逐层传参,去研究框架的源码?这显然非常费力而且会造成不可预料的东西,那么ThreadLocal就派上用场了,当然这是某些场景下,比如我的shiro中因为想改造让shiro的sessionId由我生成的Token替代,且这个Token是经过jwt对实体类数据加密后生成的,我想把这个id放到shiro的框架里面并取出来作为id,这就很难实现了,利用ThreadLocal实现就非常简单,因为能保证jwt生成后再去执行session创建工作
【1】因此先把sessionId放到ThreadLocal中

        【在登陆验证成功后执行的回调】
        ShiroUtil.createTokenOnThread(user);
        public class ShiroUtil {
                ……
               private static final ThreadLocal<String> TOKEN = new ThreadLocal<>();
               public static void createTokenOnThread(UserEntity userEntity){
                  TOKEN.set(JwtUtil.createJWT(userEntity));
               }
               public static String getTokenOnThread(){
                  return TOKEN.get();
               }
       }

【2】因此把sessionId从ThreadLocal中拿出

  【这是重写了sessionDao,当第一次创建session后会进入这里】
      protected Serializable doCreate(Session session) {
               String token=ShiroUtil.getTokenOnThread();
               assignSessionId(session, token);
               storeSession(token, session);
        return token;
      }

那么我们就利用这个本地变量线程共享的机制实现了跨组件通信,不用再一步步传参去看框架源码,后续如果有时间我会给大家逐一分享Shiro的高级应用,大家可以关注我

ThrealLocal的简单源码

【1】set部分

  很明显就是利用静态方法拿出当前线程
  public void set(T value) {
        Thread t = Thread.currentThread();//原来这就是线程隔离的根本原因
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);//将当前本地变量作为key引用拿出value,一个线程可能有多个本地变量
        else
            createMap(t, value);//如果是空在创建一个map
 }
 然后利用当前线程拿到所有本地变量的map
  ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
 }

【2】get部分

 public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//本地变量作为key拿出来
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

【3】ThreadLocalMap数据结构
显然是就是一个hashMap一样的东西
在这里插入图片描述
其他文章:
https://www.jianshu.com/p/3c5d7f09dfbd
https://www.jianshu.com/p/6fc3bba12f38
https://www.jianshu.com/p/98b68c97df9b
https://www.jianshu.com/p/e200e96a41a0
https://blog.csdn.net/zzg1229059735/article/details/82715741
https://blog.csdn.net/qjyong/article/details/2158097

GodSchool
致力于简洁的知识工程,输出高质量的知识产出,我们一起努力

原创文章 2 获赞 2 访问量 956

猜你喜欢

转载自blog.csdn.net/weixin_44275259/article/details/105820424