美文网首页
【线程专题】ThreadLocal基础、ThreadLocal应

【线程专题】ThreadLocal基础、ThreadLocal应

作者: 正版神之学习者 | 来源:发表于2020-04-28 19:52 被阅读0次

    ThrealLocal的通俗含义:将数据存到线程中的本地变量中(数据与线程绑定),且通过静态方法拿到当前线程从而拿到本地变量,然后拿到数据,实现了线程隔离;相当于每个线程拥有自己独立的数据空间,可以在里面存取数据,利用这个机制可以实现跨组件的通信,单个线程内的数据共享(不是多线程数据共享)

    image.png

    中间是本地变量,每个线程访问这个变量都会仅仅访问到自己线程内的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一样的东西


    image.png

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

    相关文章

      网友评论

          本文标题:【线程专题】ThreadLocal基础、ThreadLocal应

          本文链接:https://www.haomeiwen.com/subject/znwbwhtx.html