• 【java】ThreadLocal线程变量的实现原理和使用场景


    一.ThreadLocal线程变量的实现原理

    1.ThreadLocal核心方法有这个几个

    get()、set(value)、remove()

    2.实现原理

    ThreadLocal在每个线程都会创建一个线程内对应的T的副本,本T数据可以在本线程内任何地方可以被使用。线程之间互相不影响,所以是线程安全的。

    3.底层结构

    ThreadLocal实现各个线程数据副本的存取,是通过操作它的内部类ThreadLocalMap,进行<k,v>键值对的存取和移除。

    4.set(value)方法的底层

    public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    private void set(ThreadLocal<?> key, Object value) {
    
                // We don't use a fast path as with get() because it is at
                // least as common to use set() to create new entries as
                // it is to replace existing ones, in which case, a fast
                // path would fail more often than not.
    
                Entry[] tab = table;
                int len = tab.length;
                int i = key.threadLocalHashCode & (len-1);
    
                for (Entry e = tab[i];
                     e != null;
                     e = tab[i = nextIndex(i, len)]) {
                    ThreadLocal<?> k = e.get();
    
                    if (k == key) {
                        e.value = value;
                        return;
                    }
    
                    if (k == null) {
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }
    
                tab[i] = new Entry(key, value);
                int sz = ++size;
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash();
            }

    set(value)

      1》根据当前线程,获取本线程所拥有的TreadLocalMap,如果没有,则创建一个新的。

      2》ThreadLocalMap的<key,value>即<ThreadLocal对象,传入的value值>。【这里的ThreadLocal对象在set处,是根据本对象的hashCode经过计算获取到下标,然后循环对比Entry[]中每一个Entry的key进行插入或覆盖操作】

      3》那么可以看出结构是:

        3.1》每一个Thread有一个对应的ThreadLocalMap。Map的<K,V>即<当前ThreadLocal对象,传入的value>

        3.2》set操作根据ThreadLocal对象的hashCode对比Entry[]数组,进行新增插入或覆盖操作。

    5.get()方法底层

    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();
        }
    private Entry getEntry(ThreadLocal<?> key) {
                int i = key.threadLocalHashCode & (table.length - 1);
                Entry e = table[i];
                if (e != null && e.get() == key)
                    return e;
                else
                    return getEntryAfterMiss(key, i, e);
            }
    private T setInitialValue() {
            T value = initialValue();
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
            return value;
        }
    protected T initialValue() {
            return null;
        }

    get()

      1》根据当前Thread线程,获取本线程的ThreadLocalMap

      2》然后将<K>键,也就是本ThreadLocal作为键传入,从Map中获取value。【获取的过程即,根据ThreadLocal对象的hashCode经过计算获取下标,根据下标取出Entry[]数组中的具体值,返回结果】

      3》如果没有值,则返回null。

    6.remove()底层

    public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
         }

    remove()

      1》本线程结束以前,一定要调用remove,清除线程变量中本次的变量。防止内存泄漏

    二.ThreadLocal使用场景

    拦截器存储 调用接口的用户信息,在本次Request到达,处理,直到返回的本线程中,都可以使用线程变量中的用户信息。

    1.定义线程变量

    public class RequestData {
    
        //线程变量  租户对象
        public static final ThreadLocal<TenementUser> TENEMENT_USER = new ThreadLocal<TenementUser>();

    2.到达controller之前的拦截器中,赋值线程变量。request返回之前remove【防止内存泄漏】

    import java.net.URLDecoder;
    import java.net.URLEncoder;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    import com.alibaba.fastjson.JSON;
    import com.pisen.cloud.luna.core.enums.LunaHeaderNames;
    import com.pisen.cloud.luna.core.interceptor.utils.LunaInterceptorUtil;
    import com.pisen.cloud.luna.core.reqmodal.RequestData;
    import com.pisen.cloud.luna.core.utils.TenementUser;
    
    public class TenementAuthinterceptor implements HandlerInterceptor{
        
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                throws Exception {
            
            
            String tenInfo = request.getHeader(LunaHeaderNames.TENEMENT_INFO.getName());
            TenementUser tuser = null;
            
            if(StringUtils.isNotBlank(tenInfo)){
                
                try {
                    tenInfo = URLDecoder.decode(tenInfo, "UTF-8");    
                    tuser = JSON.parseObject(tenInfo,TenementUser.class);
                    if(tuser != null){
                        
                        if(StringUtils.isBlank(tuser.getTenementId()) || StringUtils.isBlank(tuser.getLoginName())){
                            tuser = null;
                        }else{
                            RequestData.TENEMENT_USER.set(tuser);
                        }
                    }
                    
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            
            if(tuser != null){
                return true;
            }else{
                
                String errorMsg = "登录失败,请重新登录!";
                LunaInterceptorUtil.ErrorResp(response,errorMsg);
                return false;
            }
            
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                ModelAndView modelAndView) throws Exception {
            RequestData.TENEMENT_USER.remove();
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
                throws Exception {
        }
    
    }
    View Code

    3.controller中使用线程变量

    //创建单据
        @RequestMapping(value = "/insert",method = RequestMethod.POST)
        public AjaxResult<SaleBill> insert(@RequestBody SaleBill bill){
    
            TenementUser tuser = RequestData.TENEMENT_USER.get();
  • 相关阅读:
    Cocos2d-x 3.0 屏幕触摸及消息分发机制
    stretchableImageWithLeftCapWidth气泡拉伸
    海量数据插入数据库效率对照測试 ---ADO.NET下SqlBulkCopy()对照LINQ 下InsertAllOnSubmit()
    银联+移动+三星PK微信、余额宝
    热力学三大定律与熵
    热力学三大定律与熵
    OpenGL(六) gluLookAt和gluPerspective函数解析
    特殊字符
    特殊字符
    函数的功能
  • 原文地址:https://www.cnblogs.com/sxdcgaq8080/p/10436495.html
Copyright © 2020-2023  润新知