中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

java編程ThreadLocal上下傳遞源碼分析

發布時間:2022-03-11 09:11:05 來源:億速云 閱讀:151 作者:iii 欄目:開發技術

這篇文章主要講解了“java編程ThreadLocal上下傳遞源碼分析”,文中的講解內容簡單清晰,易于學習與理解,下面請大家跟著小編的思路慢慢深入,一起來研究和學習“java編程ThreadLocal上下傳遞源碼分析”吧!

    引導語

    ThreadLocal 提供了一種方式,讓在多線程環境下,每個線程都可以擁有自己獨特的數據,并且可以在整個線程執行過程中,從上而下的傳遞。

    1、用法演示

    可能很多同學沒有使用過 ThreadLocal,我們先來演示下 ThreadLocal 的用法,demo 如下:

    /**
     * ThreadLocal 中保存的數據是 Map
     */
    static final ThreadLocal<Map<String, String>> context = new ThreadLocal<>();
    @Test
    public void testThread() {
      // 從上下文中拿出 Map
      Map<String, String> contextMap = context.get();
      if (CollectionUtils.isEmpty(contextMap)) {
        contextMap = Maps.newHashMap();
      }
      contextMap.put("key1", "value1");
      context.set(contextMap);
      log.info("key1,value1被放到上下文中");
    	// 從上下文中拿出剛才放進去的數據
      getFromComtext();
    }
    private String getFromComtext() {
      String value1 = context.get().get("key1");
      log.info("從 ThreadLocal 中取出上下文,key1 對應的值為:{}", value1);
      return value1;
    }
    //運行結果:
    demo.ninth.ThreadLocalDemo - key1,value1被放到上下文中
    demo.ninth.ThreadLocalDemo - 從 ThreadLocal 中取出上下文,key1 對應的值為:value1

    從運行結果中可以看到,key1 對應的值已經從上下文中拿到了。

    getFromComtext 方法是沒有接受任何入參的,通過 context.get().get(“key1”) 這行代碼就從上下文中拿到了 key1 的值,接下來我們一起來看下 ThreadLocal 底層是如何實現上下文的傳遞的。

    2、類結構

    2.1、類泛型

    ThreadLocal 定義類時帶有泛型,說明 ThreadLocal 可以儲存任意格式的數據,源碼如下:

    public class ThreadLocal<T> {}

    2.2、關鍵屬性

    ThreadLocal 有幾個關鍵屬性,我們一一看下:

    // threadLocalHashCode 表示當前 ThreadLocal 的 hashCode,用于計算當前 ThreadLocal 在 ThreadLocalMap 中的索引位置
    private final int threadLocalHashCode = nextHashCode();
    // 計算 ThreadLocal 的 hashCode 值(就是遞增)
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    // static + AtomicInteger 保證了在一臺機器中每個 ThreadLocal 的 threadLocalHashCode 是唯一的
    // 被 static 修飾非常關鍵,因為一個線程在處理業務的過程中,ThreadLocalMap 是會被 set 多個 ThreadLocal 的,多個 ThreadLocal 就依靠 threadLocalHashCode 進行區分
    private static AtomicInteger nextHashCode = new AtomicInteger();

    還有一個重要屬性:ThreadLocalMap,當一個線程有多個 ThreadLocal 時,需要一個容器來管理多個 ThreadLocal,ThreadLocalMap 的作用就是這個,管理線程中多個 ThreadLocal。

    2.2.1、ThreadLocalMap

    ThreadLocalMap 本身就是一個簡單的 Map 結構,key 是 ThreadLocal,value 是 ThreadLocal 保存的值,底層是數組的數據結構,源碼如下:

    // threadLocalHashCode 表示當前 ThreadLocal 的 hashCode,用于計算當前 ThreadLocal 在 ThreadLocalMap 中的索引位置
    private final int threadLocalHashCode = nextHashCode();
    // 計算 ThreadLocal 的 hashCode 值(就是遞增)
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
    // static + AtomicInteger 保證了在一臺機器中每個 ThreadLocal 的 threadLocalHashCode 是唯一的
    // 被 static 修飾非常關鍵,因為一個線程在處理業務的過程中,ThreadLocalMap 是會被 set 多個 ThreadLocal 的,多個 ThreadLocal 就依靠 threadLocalHashCode 進行區分
    private static AtomicInteger nextHashCode = new AtomicInteger();

    從源碼中看到 ThreadLocalMap 其實就是一個簡單的 Map 結構,底層是數組,有初始化大小,也有擴容閾值大小,數組的元素是 Entry,Entry 的 key 就是 ThreadLocal 的引用,value 是 ThreadLocal 的值。

    3、ThreadLocal 是如何做到線程之間數據隔離的

    ThreadLocal 是線程安全的,我們可以放心使用,主要因為是 ThreadLocalMap 是線程的屬性,我們看下線程 Thread 的源碼,如下:

    java編程ThreadLocal上下傳遞源碼分析

    從上圖中,我們可以看到 ThreadLocals.ThreadLocalMap 和 InheritableThreadLocals.ThreadLocalMap 分別是線程的屬性,所以每個線程的 ThreadLocals 都是隔離獨享的。

    父線程在創建子線程的情況下,會拷貝 inheritableThreadLocals 的值,但不會拷貝 threadLocals 的值,源碼如下:

    java編程ThreadLocal上下傳遞源碼分析

    從上圖中我們可以看到,在線程創建時,會把父線程的 inheritableThreadLocals 屬性值進行拷貝。

    4、set 方法 

    set 方法的主要作用是往當前 ThreadLocal 里面 set 值,假如當前 ThreadLocal 的泛型是 Map,那么就是往當前 ThreadLocal 里面 set map,源碼如下:

    // set 操作每個線程都是串行的,不會有線程安全的問題
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        // 當前 thradLocal 之前有設置值,直接設置,否則初始化
        if (map != null)
            map.set(this, value);
        // 初始化ThreadLocalMap
        else
            createMap(t, value);
    }

    代碼邏輯比較清晰,我們在一起來看下 ThreadLocalMap.set 的源碼,如下:

    private void set(ThreadLocal<?> key, Object value) {
        Entry[] tab = table;
        int len = tab.length;
        // 計算 key 在數組中的下標,其實就是 ThreadLocal 的 hashCode 和數組大小-1取余
        int i = key.threadLocalHashCode & (len-1);
     
        // 整體策略:查看 i 索引位置有沒有值,有值的話,索引位置 + 1,直到找到沒有值的位置
        // 這種解決 hash 沖突的策略,也導致了其在 get 時查找策略有所不同,體現在 getEntryAfterMiss 中
        for (Entry e = tab[i];
             e != null;
             // nextIndex 就是讓在不超過數組長度的基礎上,把數組的索引位置 + 1
             e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();
            // 找到內存地址一樣的 ThreadLocal,直接替換
            if (k == key) {
                e.value = value;
                return;
            }
            // 當前 key 是 null,說明 ThreadLocal 被清理了,直接替換掉
            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
        }
        // 當前 i 位置是無值的,可以被當前 thradLocal 使用
        tab[i] = new Entry(key, value);
        int sz = ++size;
        // 當數組大小大于等于擴容閾值(數組大小的三分之二)時,進行擴容
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }

    上面源碼我們注意幾點:

    • 是通過遞增的 AtomicInteger 作為 ThreadLocal 的 hashCode 的;

    • 計算數組索引位置的公式是:hashCode 取模數組大小,由于 hashCode 不斷自增,所以不同的 hashCode 大概率上會計算到同一個數組的索引位置(但這個不用擔心,在實際項目中,ThreadLocal 都很少,基本上不會沖突);

    • 通過 hashCode 計算的索引位置 i 處如果已經有值了,會從 i 開始,通過 +1 不斷的往后尋找,直到找到索引位置為空的地方,把當前 ThreadLocal 作為 key 放進去。

    好在日常工作中使用 ThreadLocal 時,常常只使用 1~2 個 ThreadLocal,通過 hash 計算出重復的數組的概率并不是很大。

    set 時的解決數組元素位置沖突的策略,也對 get 方法產生了影響,接著我們一起來看一下 get 方法。

    5、get 方法

    get 方法主要是從 ThreadLocalMap 中拿到當前 ThreadLocal 儲存的值,源碼如下:

    public T get() {
        // 因為 threadLocal 屬于線程的屬性,所以需要先把當前線程拿出來
        Thread t = Thread.currentThread();
        // 從線程中拿到 ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 從 map 中拿到 entry,由于 ThreadLocalMap 在 set 時的 hash 沖突的策略不同,導致拿的時候邏輯也不太一樣
            ThreadLocalMap.Entry e = map.getEntry(this);
            // 如果不為空,讀取當前 ThreadLocal 中保存的值
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 否則給當前線程的 ThreadLocal 初始化,并返回初始值 null
        return setInitialValue();
    }

    接著我們來看下 ThreadLocalMap 的 getEntry 方法,源碼如下:

    // 得到當前 thradLocal 對應的值,值的類型是由 thradLocal 的泛型決定的
    // 由于 thradLocalMap set 時解決數組索引位置沖突的邏輯,導致 thradLocalMap get 時的邏輯也是對應的
    // 首先嘗試根據 hashcode 取模數組大小-1 = 索引位置 i 尋找,找不到的話,自旋把 i+1,直到找到索引位置不為空為止
    private Entry getEntry(ThreadLocal<?> key) {
        // 計算索引位置:ThreadLocal 的 hashCode 取模數組大小-1
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        // e 不為空,并且 e 的 ThreadLocal 的內存地址和 key 相同,直接返回,否則就是沒有找到,繼續通過 getEntryAfterMiss 方法找
        if (e != null && e.get() == key)
            return e;
        else
        // 這個取數據的邏輯,是因為 set 時數組索引位置沖突造成的  
            return getEntryAfterMiss(key, i, e);
    }
    // 自旋 i+1,直到找到為止
    private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
        Entry[] tab = table;
        int len = tab.length;
        // 在大量使用不同 key 的 ThreadLocal 時,其實還蠻耗性能的
        while (e != null) {
            ThreadLocal<?> k = e.get();
            // 內存地址一樣,表示找到了
            if (k == key)
                return e;
            // 刪除沒用的 key
            if (k == null)
                expungeStaleEntry(i);
            // 繼續使索引位置 + 1
            else
                i = nextIndex(i, len);
            e = tab[i];
        }
        return null;
    }

    get 邏輯源碼中注釋已經寫的很清楚了,我們就不重復說了。

    6、擴容

    ThreadLocalMap 中的 ThreadLocal 的個數超過閾值時,ThreadLocalMap 就要開始擴容了,我們一起來看下擴容的邏輯:

    //擴容
    private void resize() {
        // 拿出舊的數組
        Entry[] oldTab = table;
        int oldLen = oldTab.length;
        // 新數組的大小為老數組的兩倍
        int newLen = oldLen * 2;
        // 初始化新數組
        Entry[] newTab = new Entry[newLen];
        int count = 0;
        // 老數組的值拷貝到新數組上
        for (int j = 0; j < oldLen; ++j) {
            Entry e = oldTab[j];
            if (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == null) {
                    e.value = null; // Help the GC
                } else {
                    // 計算 ThreadLocal 在新數組中的位置
                    int h = k.threadLocalHashCode & (newLen - 1);
                    // 如果索引 h 的位置值不為空,往后+1,直到找到值為空的索引位置
                    while (newTab[h] != null)
                        h = nextIndex(h, newLen);
                    // 給新數組賦值
                    newTab[h] = e;
                    count++;
                }
            }
        }
        // 給新數組初始化下次擴容閾值,為數組長度的三分之二
        setThreshold(newLen);
        size = count;
        table = newTab;
    }

    源碼注解也比較清晰,我們注意兩點:

    • 擴容后數組大小是原來數組的兩倍;

    • 擴容時是絕對沒有線程安全問題的,因為 ThreadLocalMap 是線程的一個屬性,一個線程同一時刻只能對 ThreadLocalMap 進行操作,因為同一個線程執行業務邏輯必然是串行的,那么操作 ThreadLocalMap 必然也是串行的。

    感謝各位的閱讀,以上就是“java編程ThreadLocal上下傳遞源碼分析”的內容了,經過本文的學習后,相信大家對java編程ThreadLocal上下傳遞源碼分析這一問題有了更深刻的體會,具體使用情況還需要大家實踐驗證。這里是億速云,小編將為大家推送更多相關知識點的文章,歡迎關注!

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    内丘县| 张家界市| 怀安县| 镶黄旗| 获嘉县| 武川县| 武功县| 三原县| 界首市| 石首市| 新昌县| 上虞市| 珠海市| 万山特区| 平湖市| 彝良县| 沂源县| 西和县| 苏尼特左旗| 平罗县| 青阳县| 修武县| 会昌县| 仙居县| 祁门县| 巢湖市| 扎鲁特旗| 合肥市| 武清区| 吐鲁番市| 泗洪县| 昭通市| 博兴县| 扬中市| 银川市| 青神县| 吴江市| 康保县| 兴仁县| 友谊县| 兰州市|