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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM