首頁 > Java > java教程 > ThreadLocal的實作原理的分析介紹(附程式碼)

ThreadLocal的實作原理的分析介紹(附程式碼)

不言
發布: 2019-02-16 13:37:47
轉載
3008 人瀏覽過

這篇文章帶給大家的內容是關於ThreadLocal的實現原理的分析介紹(附程式碼),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

ThreadLocal,即執行緒局部變量,用來為每一個使用它的執行緒維護一個獨立的變數副本。這種變數只在執行緒的生命週期內有效。且與鎖定機制那種以時間換取空間的做法不同,ThreadLocal沒有任何鎖定機制,它以空間換取時間的方式保證變數的執行緒安全性。

本篇從原始碼方面分析ThreadLocal的實作原理。

先看ThreadLocal類別圖結構

  


  1. #SuppliedThreadLocal主要是JDK1.8用來擴展對Lambda表達式的支持,有興趣的自行百度。 ThreadLocalMap是ThreadLocal的靜態內部類別,也是實際保存變數的類別。

  2. Entry是ThreadLocalMap的靜態內部類別。 ThreadLocalMap持有一個Entry數組,以ThreadLocal為key,變數為value,封裝一個Entry。
  3. 下面以一張圖簡單說明Thread,ThreadLocal,ThreadLocalMap和Entry的關係。

      
  4. #說明上圖:
  5. 一個Thread擁有一個ThreadLocalMap物件

#ThreadLocalMap擁有一個Entry陣列

每個Entry都有k--v

Entry的key就是某個具體的ThreadLocal物件

#以下分析主要方法。

  1. 1、set()

  2. public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    登入後複製

    這裡可以看出:一個Thread只擁有一個ThreadLocalMap物件;具體的存值呼叫是ThreadLocalMap的set(),傳入的參數key就是目前ThreadLocal物件。
  3. 再看看ThreadLocalMap的set()方法:

  4. #
    private void set(ThreadLocal<?> key, Object value) {
                Entry[] tab = table;
                int len = tab.length;
                int i = key.threadLocalHashCode & (len-1); // 1
    
                for (Entry e = tab[i];  // 2
                     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); // 3
                int sz = ++size;
                if (!cleanSomeSlots(i, sz) && sz >= threshold) // 4
                    rehash();
            }
    登入後複製

  5. ##透過key的hashCode與陣列容量-1 取模,計算陣列index

#從目前index開始遍歷,清除key為null的無效Entry

將K-V封裝為Entry,放入陣列

判斷是否需要進行Entry數組擴容。 threshold的值為陣列容量的2/3。

  看看擴容的resize()方法:

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 {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

            setThreshold(newLen);
            size = count;
            table = newTab;
        }
登入後複製

#這裡主要就是擴容為原先的2倍。然後遍歷舊數組,根據新數組容量重新計算Entry在新數組中的位置。

    2、get()
  1. #ThreadLocal的get()方法如下:
  2. 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();
        }
    登入後複製

  3. ThreadLocalMap的getEntry()方法如下:

  4. private Entry getEntry(ThreadLocal<?> key) {
                int i = key.threadLocalHashCode & (table.length - 1); // 1
                Entry e = table[i];
                if (e != null && e.get() == key) // 2
                    return e;
                else
                    return getEntryAfterMiss(key, i, e); //3
            }
    
    private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
                Entry[] tab = table;
                int len = tab.length;
    
                while (e != null) { //4
                    ThreadLocal<?> k = e.get();
                    if (k == key)
                        return e;
                    if (k == null)
                        expungeStaleEntry(i);
                    else
                        i = nextIndex(i, len);
                    e = tab[i];
                }
                return null;
            }
    登入後複製
  5. #計算index

目前index上的Entry不為空且key相同,直接回傳

否則去相鄰index尋找

#循環找,發現無效key就清除。找到就結束循環。

3、remove()

#########
public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
private void remove(ThreadLocal<?> key) {
            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)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }
登入後複製
######## #處理方式和尋找儲存類似,刪除對應Entry後都會移除key為null的無效元素。 ############注意###############
static class Entry extends WeakReference<ThreadLocal<?>> {}
登入後複製
#########ThreadLocal可能有OOM問題。因為ThreadLocalMap是使用ThreadLocal的弱引用作為key的,發生GC時,key被回收,這樣我們就無法存取key為null的value元素,如果value本身是較大的對象,那麼線程一直不結束的話,value就一直無法得到回收。特別是在我們使用線程池時,線程是複用的,不會殺死線程,這樣ThreadLocal弱引用被回收時,value不會被回收。 ######在使用ThreadLocal時,執行緒邏輯程式碼結束時,必須顯示呼叫ThreadLocal.remove()方法。 ###

以上是ThreadLocal的實作原理的分析介紹(附程式碼)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:cnblogs.com
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板