ThreadLocal原始碼解讀

z1340954953發表於2018-04-26

ThreadLocal的三個理論基礎

1. 每個執行緒都有一個自己的ThreadLocal.ThreadLocalMap物件,ThreadLocal類中定義了靜態類ThreadLocalMap,

靜態類ThreadLocalMap中定義了Entry結構儲存

static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

2. 每個ThreadLocal物件都有一個迴圈計數器

3. ThreadLocal.get()取值,就是根據當前的執行緒,獲取執行緒中自己的ThreadLocal.ThreadLocalMap,然後在這個Map中根據根據第二點中迴圈計數器取得一個特定value值

兩個數學問題

1. ThreadLocal.ThreadLocalMap規定了table的大小必須是2的N次冪

/**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

計算機處理位運算的效率比數學運算要高,例如ThreadLocalMap中獲取Entry物件的方法

 private Entry getEntry(ThreadLocal key) {
            int i = key.threadLocalHashCode & (table.length - 1);//1
            Entry e = table[i];
            if (e != null && e.get() == key)
                return e;
            else
                return getEntryAfterMiss(key, i, e);
        }

上面第一行程式碼,通過與運算獲取到Entry在陣列中的索引

如果使用%的話,table.length=16,存在一個數字23,23%16=7,如果轉為上面的二進位制運算的話:

23                 -> 00010111 

&

table-1 = 15 -> 00001111

result: 00000111 就是十進位制的 7 ,效率更高

2. 對於上面取模獲取在table中索引位置時候,threadLocalhashCode原始碼如下:

private final int threadLocalHashCode = nextHashCode();
 private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }
 private static final int HASH_INCREMENT = 0x61c88647;

length 為 16 和 32 時候,取模生成的值


通過取模方式獲取索引的時候,每次都會在原來的threadLocalHashCode的基礎上加上0x61c88647,這樣的結果是生成的hash值分散,而且在length擴容為,2的n次冪,之後,生成的hash值會和前面擴容前的值一致,這就保證了threadLocalHashCode可以從任何地方開始。

set(T 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);
    }
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

1. 獲取當前執行緒

2. 獲取當前執行緒的ThreadLocalMap,不為null,就往裡面設值

3. 否則,就去建立ThreadLocalMap,並設值

看下,第三步的原始碼:

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

建立一個Entry陣列,將初始值放入,取模運算後的索引位置,並且置entry數量為1,而且從ThreadLocalMap中看出並沒有next節點,也就是ThreadLocalMap不是類似於hashmap的連結串列結構,而是開地址法,每次遞增一個值,取模運算計算索引存放元素。這樣的結果就是設定同一個value放到table中的位置會不一樣的

接著,看下,ThreadLocalMap的設值原始碼:

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

1. 取模運算獲取到table陣列的索引位置,從這個索引位置開始遍歷到陣列最後

2. 根據獲取每個Entry的ThreadLocal引用,對於Entry它是個弱引用

static class Entry extends WeakReference<ThreadLocal> {

,獲取到ThreadLocal

3. 如果key和k是指向同一個ThreadLocal,那麼就將值設定到這個Entry上,返回

4. 不是同一個ThreadLocal,在判斷下位置上的ThreadLocal是不是空的,因為Entry是弱引用,有可能這個ThreadLocal已經被垃圾回收了,如果ThreadLocal是空的,會去輪詢找到下一個不為null的entry,將值放在這個entry,並且會去將過期的entry刪除

5. 如果上面都沒有返回的話,將entry數量加1 ,在索引位置設定一個新的Entry

get()

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        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 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;
        }

1. 獲取到當前執行緒繫結的ThreadLocalMap

2. 如果ThreadLocalMap不為空

  * 如果根據index能直接找到Entry,並且不為null,直接返回這個Entry

  * 否則,繼續向下遍歷,找到下一個不為null的entry,返回這個entry,並在輪詢過程中將過期的entry刪除

3. 如果ThreadLocalMap為空

給當前執行緒建立一個ThreadLocalMap,並設定初值

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;
                }
            }
        }

1 . 找到執行緒繫結的ThreadLocalMap,並且找到對應的entry,刪除掉這個entry

總結

1. ThreadLocal不需要鍵值,因為ThreadLocalMap不是通過連結串列實現,而是通過開地址法實現的

2. 設定值的時候,如果index位置找到entry,並且key(這個key指的是ThreadLocal引用)是同一個,進行覆蓋,否則向下找到一個不為null的entry,並設值

3. 查詢資料的時候,如果能夠從index找到entry,直接就返回了,否則就向下繼續找到下一個不為null的entry,返回

4. 如果需要向ThreadLocal中存放不同型別的資料,需要定義多個ThreadLocal

參考部落格:

http://www.cnblogs.com/xrq730/p/4854813.html












相關文章