Java ThreadLocal原始碼解析: ThreadLocalMap
阿新 • • 發佈:2018-11-21
ThreadLocalMap在比其中Thread和ThreadLocal部分要複雜很多,是ThreadLocal底層儲存和核心資料結構。從整體上將,ThreadLocalMap底層是Entry陣列,key值為ThreadLocal的hash code, 採用線性探測法解決雜湊衝突。
以下是ThreadLocalMap核心屬性和方法,所有方法和屬性都標識為private,僅為ThreadLocal可以訪問:
ThreadLocalMap核心屬性分析,主要包括底層儲存資料結構,相關閾值計算,但負載因子計算貌似有點怪:
/** * Entry[] table陣列的初始大小為16,這個數值必須為2的冪次方 */ private static final int INITIAL_CAPACITY = 16; /** * 最終落地儲存的資料結構是陣列,其中Entry是個內部類,可以理解為key-value結構, * 這個陣列的長度必須是2的冪 */ private Entry[] table; /** * 陣列中實際佔用了的個數 */ private int size = 0; /** * 閾值,達到這個閾值,將對陣列進行擴容,因此需要進行rehash */ private int threshold; // Default to 0 /** * 這個方法就是設定threshold,是總長度的2/3 */ private void setThreshold(int len) { threshold = len * 2 / 3; } // 這些都是用來解決衝突的,說白了如果i這個位置被佔了,獲取i+1位置 private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); } private static int prevIndex(int i, int len) { return ((i - 1 >= 0) ? i - 1 : len - 1); }
以下為ThreadLocalMap的建構函式,分別是通過ThreadLocal和value值,和通過已有ThreadLocalMap構造:
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { // 實際上就是key value對 table = new Entry[INITIAL_CAPACITY]; // 以初始化大小建立Entry陣列 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 生成hash值,這個值為啥要選這個值? table[i] = new Entry(firstKey, firstValue); // 設定找到的位置的值 size = 1; // 大小為1 setThreshold(INITIAL_CAPACITY); // 更新擴容閾值 } private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { // 如果存在值 @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); // 獲取ThreadLocal if (key != null) { // 如果為空了就應該被回收掉 Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); // 獲取hash值 while (table[h] != null) // 如果這個位置已經被佔用了,訪問下一個位置,直到不衝突,最終的效果是迴圈找 h = nextIndex(h, len); // 當然是解決衝突 table[h] = c; size++; } } } } 以下是ThreadLocalMap中核心操作方法,包括get,remove,rehash等基礎實現: private Entry getEntry(ThreadLocal<?> key) { // 根據ThreadLocal獲取value值 int i = key.threadLocalHashCode & (table.length - 1); // 獲取訪問index Entry e = table[i]; if (e != null && e.get() == key) // 如果找到,並且驗證 return e; else // 否者執行反向的hash的過程,其實就是從這個位置一個一個往下找 return getEntryAfterMiss(key, i, e); } private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { // 迴圈從i位置往下找 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; } private int expungeStaleEntry(int staleSlot) { // 擦除失效的節點 Entry[] tab = table; int len = tab.length; // expunge entry at staleSlot tab[staleSlot].value = null; // staleSlot這個位置失效了,置為空,size減一 tab[staleSlot] = null; size--; // Rehash until we encounter null Entry e; int i; for (i = nextIndex(staleSlot, len); // 後面的邏輯就是從這個位置開始作rehash, 因為這個空了,如果後面有有效的,拿上來填上 (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal<?> k = e.get(); if (k == null) { e.value = null; tab[i] = null; size--; } else { int h = k.threadLocalHashCode & (len - 1); if (h != i) { tab[i] = null; while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } return i; } private void set(ThreadLocal<?> key, Object value) { // 找到合適位置安放當前值,如果有失效的點, 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) { // 如果當前這個ThreadLocal已經有了,將新value替換之前的 e.value = value; return; } if (k == null) { // 如果找到了一個key為空的位置,這個位置失效了,替換掉這個失效的位置 replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) // rehash(); // 大於threshold的0.75就重新hash } 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; } } } private void rehash() { expungeStaleEntries(); if (size >= threshold - threshold / 4) // 這裡有個數組的總長度,閾值,rehash的閾值,大概是總長度的2/3*0.75時才rehash resize(); } /** * Double the capacity of the table. */ private void resize() { Entry[] oldTab = table; int oldLen = oldTab.length; int newLen = oldLen * 2; // 擴容2倍 Entry[] newTab = new Entry[newLen]; int count = 0; for (int j = 0; j < oldLen; ++j) { // 重新hash 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; }
總結:ThreadLocalMap的實現也不是很複雜,底層為一個數組,通過ThreadLocal的threadLocalHashCode作雜湊,如果發現有實效的,觸發清除失效值,達到閾值觸發rehash,使用線性探測法定位hash位置,即通過hash值獲取陣列中的一個index位置,如果已被佔用就去下一個位置,如果發現失效的,觸發清除失效值。但是這裡的負載因子有點怪,看起來需要經過兩次計算,是2/3*0.75,及0.5