ThreadLocal 源碼分析

引言

ThreadLocal 可以在每個線程中存取數據,并且不同的線程中的數據互不影響。使用在數據以線程為作用域并且不同的線程擁有不用的數據副本,或者是復雜的參數傳遞時(參數在同一線程中的不同類中傳遞)。

在分析消息機制源碼的時候,涉及到了 ThreadLocal,使用是在 Looper 類中通過 ThreadLocal<Looper> 對象的 set 方法和 get 方法存取當前線程的 Looper 對象

我們發現 ThreadLocal 對象是一個靜態的對象,說明每個線程都可以通過該對象來存取當前線程對應的 Looper ,說明 ThreadLooper 中存放的數據確實是以線程為作用域的,源碼接著看

源碼分析

看源碼之前,先大體的說一下工作的原理。每個線程類 Thread 中都保存了一個 ThreadLocalMap ,可以理解為就是一個 Map,Map 的 key 就是這個 Thread 中所有使用到的 ThreadLocal 對象,Map 的 value 是 Thread 中使用該 ThreadLocal 存儲的數據的值。在使用時通過 ThreadLocal 即可存取對應的 value。

可以這么理解。但是 ThreadlocalMap 并不是一個 Map,其內部通過一個 Entry 類型的數組 Entry[] 來存儲 ThreadLocal 和其存儲的值,Entry 中保存了 value 和 ThreadLocal,Entry 是繼承了 WeakReference ,也就是說 Entry[] 是以軟弱引用來保存 Entry 的,并且數組中的索引是通過 ThreadLocal 的 hashCode 計算的值,這樣根據 ThreadLocal 的 hashCode 也就有了與 Entry[] 中每個索引位置的值之間的關系。

ThreadLocalMap 存在的意義不僅僅是保存 Entry[] 數組,也為該數組中數據的存取提供了更多計算方法

// Entry 中只保存了 value,super 方法中會保存 ThreadLocal
static class Entry extends WeakReference<ThreadLocal> {
    /** The value associated with this ThreadLocal. */
    Object value;
        Entry(ThreadLocal k, Object v) {
        super(k);
        value = v;
    }
}

set() 方法用于數據的存儲

// ThreadLocal 的 set() 方法
public void set(T value) {
    Thread t = Thread.currentThread(); // 獲取當前線程
    ThreadLocalMap map = getMap(t); // 從線程中取出 ThreadLocalMap
    if (map != null)
        map.set(this, value); // 如果 ThreadLocalMap 不為 null ,則使用 ThreadLocalMap 存儲
    else
        createMap(t, value); // 如果 ThreadLocalMap 為 null,則為該線程初始化 ThreadLocalMap ,并將數據存儲
}


// ThreadLocal 的 getMap() 方法
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}


// ThreadLocalMap 的 set() 方法
private void set(ThreadLocal key, Object value) {

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1); // 根據 ThreadLocal 計算在 Entry[] 數組中的索引值

    // 如果該索引位置有值,則判斷 ThreadLocal 是否匹配,不匹配則遍歷到下一有值位置
    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { 
        ThreadLocal k = e.get();

        // 如果有值則判斷當前位置的 ThreadLocal 和需要插入的 ThreadLocal 是否相同,如果相同則直接修改 value 為新值
        if (k == key) { 
            e.value = value;
            return;
        }

        // 如果對應位置 ThreadLocal 為空,則取代舊的 Entry ,重新賦值新的 Entry
        if (k == null) {
            replaceStaleEntry(key, value, i); 
            return;
        }
    }

    // 如果該索引位置沒有值,則直接賦值為新值
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}


// ThreadLocal 的 createMap() 方法
void createMap(Thread t, T firstValue) { // Thread 的 ThreadLocalMap 為 null 時,為 Thread 創建新的 ThreadLocal 并將 ThreadLocal 及 Value 存儲
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

// ThreadLocalMap 的構造方法
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY]; // 初始化 Entry[] 
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 根據 ThreadLocal 計算索引值
    table[i] = new Entry(firstKey, firstValue); 為該索引位置賦值
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

通過在 Thread 中獲取 ThreadLocalMap ,再根據 TheadLocal 和 Value 將數據存儲到了 Entry[] 數組中,具體的存儲過程看注釋。

有一個關鍵點,存儲的時候,如果 ThreadLocal 對應的位置有值,則會查看向下一個位置,如果該位置還是有值則繼續向下一位置,直到沒有值的位置插入該 Entry

存:判斷 ThreadLocalMap 是否為空
  • 為空:創建 ThreadLocalMap 并為對應位置賦值

  • 不為空:判斷當前位置是否有值 Entry

    • 無值:為當前位置賦值為新的 Entry
    • 有值:判斷 ThreadLocal 是否對應
      • 對應:修改舊 Value 值
      • 不對應:向下一有值索引位置判斷,直到 ThreadLocal 對應(修改原 Value 值) 或遇到該索引對應值無 ThreadLocal 則為該位置賦值新 Entry,或遇到無值索引時為該位置賦值新 Entry

get() 方法源碼解析

// ThreadLocal 的 get() 方法
public T get() {
    Thread t = Thread.currentThread();  // 獲取當前線程 Thread
    ThreadLocalMap map = getMap(t); // 獲取該線程中的 ThreadLocalMap
    if (map != null) { // 如果 ThreadLocalMap 不為空
        ThreadLocalMap.Entry e = map.getEntry(this); 調用 ThreadLocalMap 的 getEntry 方法獲取 Entry 對象
        if (e != null) // Entry 不為空則將 Entry 中存儲的 Value 返回
            return (T)e.value;
    }
    return setInitialValue(); // 線程的 ThreadLocalMap 為空或者 ThreadLocalMap 中無對應 Entry 情況
}

// ThreadLocalMap 的 getEntry() 方法
private Entry getEntry(ThreadLocal key) {
    int i = key.threadLocalHashCode & (table.length - 1); // 計算在 Entry[] 中的索引
    Entry e = table[i];
    if (e != null && e.get() == key) // 如果 Entry 不為 null 且當前位置對應的 ThreadLocal 為該 ThreadLocal 則返回 Entry
        return e;
    else 
        return getEntryAfterMiss(key, i, e); // 該位置不對應則向下一位置遍歷查詢
}

// ThreadLocalMap 的 getEntryAfterMiss() 方法
// Entry 為 null 或 ThreadLocal 不對應則向下一個位置遍歷,直到 Entry 的 ThreadLocal 和當前 ThreadLocal 對應或遍歷結束,如果遍歷結束還是 null 則返回 null
private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
        while (e != null) {
        ThreadLocal k = e.get();
        if (k == key)
            return e;
        if (k == null)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}


// ThreadLocalMap 的 setInitialValue() 方發法,線程的 ThreadLocalMap 為空或者 ThreadLocalMap 中無對應 Entry 情況
private T setInitialValue() {
    T value = initialValue(); // 該方法默認返回 null,可重新該方法修改默認值
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);  // ThreadLocalMap 不為空則為該位置創建值為默認值的 Entry
    else
        createMap(t, value); // ThreadLocalMap 為空時,則為該線程創建 ThreadLocalMap 并未該位置創建值為默認值的 Entry
    return value; // 將默認值返回
}

get() 方法主要為從 ThreadLocalMap 中取出對應的值,在該位置沒有值或當前線程無對應 ThreadLocalMap 情況下,會返回默認的值 null

取,判斷 ThreadLocalMap 是否為空
  • 為空

    • 創建新 ThreadLocalMap 并將該位置賦值 Value 為默認值的 新 Entry ,并返回
  • 不為空

    • 根據 ThreadLocal 計算索引,判斷該索引位置是否有值
      • 無值:為該索引位置賦值 Value 為默認值的 新 Entry ,并返回
      • 有值:判斷 ThreadLocal 是否匹配
        • 匹配:返回對應位置 Value
        • 不匹配:遍歷下一位置直到 ThreadLocal 匹配時返回該 Entry,或該位置的 Entry 的 ThreadLocal 為空時刪除該位置之后的所有空條目,繼續向下一位置遍歷,遍歷結束如果還是沒有對應 ThreadLocal 則返回 null
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,321評論 6 543
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,559評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,442評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,835評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,581評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,922評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,931評論 3 447
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,096評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,639評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,374評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,591評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,104評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,789評論 3 349
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,196評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,524評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,322評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,554評論 2 379

推薦閱讀更多精彩內容