1. 程式人生 > >ThreadLocal 原始碼解析

ThreadLocal 原始碼解析

引言

ThreadLocal 可以在每個執行緒中存取資料,並且不同的執行緒中的資料互不影響。使用在資料以執行緒為作用域並且不同的執行緒擁有不用的資料副本,或者是複雜的引數傳遞時(引數在同一執行緒中的不同類中傳遞)。

在分析訊息機制原始碼的時候,涉及到了 ThreadLocal,使用是在 Looper 類中通過 ThreadLocal 物件的 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 並且 Entry 持有 ThreadLocal 的弱引用,Entry 陣列中的索引是通過 ThreadLocal 的 hashCode 計算的值,這樣根據 ThreadLocal 的 hashCode 也就有了與 Entry[] 中每個索引位置的值之間的關係。

ThreadLocalMap 存在的意義不僅僅是儲存 Entry[] 陣列,也為該陣列中資料的存取提供了更多計算方法

// Entry 中只儲存了 value
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