ThreadLocal源码解析2.ThreadLocalMap
                                                            生活随笔
收集整理的這篇文章主要介紹了
                                ThreadLocal源码解析2.ThreadLocalMap
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.                        
                                1.ThreadLocalMap內部結構
static class ThreadLocalMap {/** 這里的Entry繼承了弱引用,(弱引用只要發生GC就要被回收)* Entry的key(ThreadLocal)以弱引用的方式指向ThreadLocal對象。* (可以避免內存泄露)* (參考 https://blog.csdn.net/qq_46312987/article/details/117166100)*/static class Entry extends WeakReference<ThreadLocal<?>> {Object value; Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}//Entry數組初始長度,數組的長度必然是2的次方數private static final int INITIAL_CAPACITY = 16;//Entry數組private Entry[] table;//Entry數組中的元素個數private int size = 0;/** 擴容閾值 初始值為 len * 2/3* 觸發后調用 rehash()方法* rehash() 方法先做一次全局檢查過期數據,把散列表中所有過期的entry移除* 如果移除之后 當前散列表中的entry個數仍然達到 閾值的3/4,就進行擴容。*/private int threshold; //設置閾值為當前數組長度的2/3private void setThreshold(int len) {threshold = len * 2 / 3;}/** @param i 當前下標* @param len 數組長度* 返回當前位置的下一個位置*/private static int nextIndex(int i, int len) {/** 當前下標+1 如果小于數組長度的話 返回 +1的值* 如果等于數組長度的話,就返回0,即形成了環*/return ((i + 1 < len) ? i + 1 : 0);}/** 跟nextIndex()方法類似。返回當前位置的上一個位置*/private static int prevIndex(int i, int len) {return ((i - 1 >= 0) ? i - 1 : len - 1);}/** 構造方法*/ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {//構造一個初始長度為16的Entry數組table = new Entry[INITIAL_CAPACITY];//尋址,(hash值 & length - 1)int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//構造一個Entry對象放到指定的位置上table[i] = new Entry(firstKey, firstValue);//size = 1 size = 1;//設置閾值為初始化容量(16)的2 / 3 = 10setThreshold(INITIAL_CAPACITY);}}2.getEntry()方法詳解
//----------------------------ThreadLocal.get()------------------------------- // 此方法詳解在 [https://blog.csdn.net/qq_46312987/article/details/121799343] // 這里我們詳細講解 getEntry()方法。public T get() {//獲取當前線程對象Thread t = Thread.currentThread();//獲取當前線程內部的threadLcoalMapThreadLocalMap map = getMap(t);//內部的map不為NULL,if (map != null) {//這里調用了getEntry(),去threadLocalMap中查詢指定的數據ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();} //-----------------------------ThreadLocalMap.getEntry()---------------------/** ThreadLocal對象的get()操作實際上是由ThreadLocalMap.getEntry()完成的。*/ private Entry getEntry(ThreadLocal<?> key) {//尋址int i = key.threadLocalHashCode & (table.length - 1);//獲取指定位置的EntryEntry e = table[i];/** 當前位置不為NULL 并且當前位置的key(ThreadLocal對象)與傳來的key是否一致* 注意 ThreadLocalMap與HashMap或者是ConcurrentHashMap最大的區別就是* 當出現hash沖突時使用的是開放尋址法(找下一個位置)而不是拉鏈法(在當前位置形成 * 鏈表)*/if (e != null && e.get() == key)return e;else//在當前位置沒找到就去后面繼續找。return getEntryAfterMiss(key, i, e);}//-----------------------ThreadLocalMap.getEntryAfterMiss()-------------------/** @param key ThreadLocal對象* @param i 第一次尋址的索引位置* @param e table第i個位置的entry對象* * 此方法的作用就是從當前位置向后查詢,查詢到指定數據返回,當查詢到某一個位置的 * Entry為NULL時結束,最終返回NULL,在查詢過程中如果某一個位置的entry不為NULL,* 但是key為NULL,說明對應的當前entry關聯的ThreadLcoal對象已經被回收了,那么就 * 會將當前的entry清理掉*/ private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {Entry[] tab = table;int len = tab.length;//向后查找如果還沒有查找到 但是當前位置的entry為NUll,說明查找不到,結束循環while (e != null) {//entry繼承了弱引用,get()方法就是獲取內部的ThreadLocal對象ThreadLocal<?> k = e.get();//查找成功,返回entry。if (k == key)return e;/* key是NULL,說明entry關聯的ThreadLocal被回收了,但是entry還存在,* 這時就需要將當前位置的entry干掉。*/if (k == null)expungeStaleEntry(i);//k不為NULL,但是當前entry不是目標entry,繼續向后查找elsei = nextIndex(i, len);e = tab[i];}return null;}//--------------------------ThreadLocalMap.expungeStaleEntry()----------------/** @param staleSlot (stale翻譯為過期的) 此方法的作用* 1.即當前位置上的entry的key是NULL,說明當前的entry已經沒有用了。* 需要將其干掉。* 2.遍歷哈希表,將從當前位置開始的entry != null && key == null的所有entry * 干掉,然后將正常的entry做一次重新遷移,優化查詢。 */private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;int len = tab.length;//將當前位置的entry的的value置為NULL(help GC)tab[staleSlot].value = null;//將當前位置置為NULL,將entry直接干掉tab[staleSlot] = null;//size - 1size--;//下面就是rehash()的過程//當前遍歷的entryEntry e; //當前的位置int i;//循環遍歷數組(從置為NULL的entry對象的下一個位置開始),直到某一位置上的entry為NULL為止。for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {/** 能進入for循環,說明當前位置的entry一定不為NULL。*/ThreadLocal<?> k = e.get();//k(ThreadLocal對象) = NULL,將當前entry的value和當前entry全部干掉。if (k == null) {e.value = null;tab[i] = null;size --;} else {/** 執行到這里,說明當前位置的entry不為NULL。* 此時需要執行的事就是判斷當前位置上的entry是否在經過哈希尋址后應 * 該在的位置,(因為有可能發生過沖突),如果不在該在的位置,就去尋找* 距離尋址位置最近的位置(也可能找到尋址的位置)。*///重新計算索引int h = k.threadLocalHashCode & (len - 1);//條件成立 表示當前entry確實不在該在的位置,需要嘗試重新找位置存放。if (h != i) {tab[i] = null;//循環找位置存放while (tab[h] != null)h = nextIndex(h, len);tab[h] = e;}}}return i;}3.getEntry()運行流程圖
4.set()方法詳解
//-----------------ThreadLocalMap.set()---------------------------------------private void set(ThreadLocal<?> key, Object value) {//尋址Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);/** for循環做的事就是,循環尋找key相同的entry。* 1.找到相同key并且正常的entry,做value替換* 2.找到某一位置(entry != null && entry.key == NULL),將entry替換。*/for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();//找到了相同的key,替換value。if (k == key) {e.value = value;return;}//查找過程中,碰到了entry.key == null,說明當前entry是過期數據if (k == null) {//替換過期的entry。replaceStaleEntry(key, value, i);return;}}/** 執行到這里,說明for循環找到了一個當前slot為NULL的情況,* 此時直接在這個slot位置上創建一個Entry對象。*/tab[i] = new Entry(key, value);int sz = ++size;/** 這里做一次清理工作,cleanSomeSlots()返回true表示內部沒有清理到數據* 這時在判斷元素數量是否達到了擴容閾值,大于等于閾值就進行rehash()操作*/if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();}//----------------------ThreadLocalMap.replaceStaleEntry----------------------/** 替換過期的entry。* @param key 新key* @param value 新value* @param staleSlot 過期entry的位置*/private void replaceStaleEntryreplaceStaleEntry (ThreadLocal<?> key, Object value,int staleSlot) {Entry[] tab = table;int len = tab.length;Entry e;//將過期entry的位置賦值給slotToExpungeint slotToExpunge = staleSlot;/** 以當前staleSlot位置的前一個位置開始,向前迭代查找,* (結束條件entry = null),更新slotToExpunge為靠前的* (entry != null && entry.key == null) 的過期entry的位置。*/for (int i = prevIndex(staleSlot, len);(e = tab[i]) != null;i = prevIndex(i, len)){if (e.get() == null){slotToExpunge = i;}}/** 從當前不合法的entry的位置的下一個位置開始遍歷,* */for (int i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {//獲取當前位置的entry的key(threadLocal)ThreadLocal<?> k = e.get();/** key要添加的新key* k 當前遍歷的key* k == key 說明要添加的key已經存在了,需要替換value* 然后做清理邏輯*/if (k == key) {//替換value。e.value = value; //將過期的entry放到當前位置i,因為下面要從i這個位置開始清理tab[i] = tab[staleSlot];//將替換完畢的entry放到過期數據的位置tab[staleSlot] = e;//條件成立:說明replaceStaleEntry一開始時向前查找過期數據時,并未 //找到過期的entry.if (slotToExpunge == staleSlot)//因為上面做了交換,所以當前位置i就是過期數據,賦值給slotToExpungeslotToExpunge = i;//下面就是清理過期的entry。cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);return;}/* * 條件1: k == null成立,說明當前遍歷的entry是一個過期數據* 條件2: slotToExpunge == staleSlot成立說明一開始向前查找過期數據 * 并未找到過期的entry*/if (k == null && slotToExpunge == staleSlot)//因為向后查詢過程中查找到了一個過期數據,更新slotToExpunge為當前位 //置,前提條件是前驅掃描時未發現過期數據slotToExpunge = i;}/** 什么時候執行到這里?*—>向后查找過程中,并未發現 key = null 的entry,說明當前set操作是一個添加* 邏輯,直接將新數據添加到過期entry的位置上。*/tab[staleSlot].value = null;tab[staleSlot] = new Entry(key, value);//條件成立:說明除了當前staleSlot過期entry位置以外,還發現其他的過期slot了if (slotToExpunge != staleSlot)//開啟清理數據的邏輯。cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);}//------------------ThreadLocalMap.cleanSomeSlots----------------------------/** @param i 表示清理工作的起始位置,這個位置一定是NULL。* @param n 表示table.length*/ private boolean cleanSomeSlots(int i, int n) {boolean removed = false;Entry[] tab = table;int len = tab.length;do {//獲取當前i位置的下一個位置i = nextIndex(i, len);//獲取位置上的entry。Entry e = tab[i];/** 條件成立表示當前位置的entry是過期數據,* 需要清理 */if (e != null && e.get() == null) {n = len; //清理表示置為true,表示清理過removed = true;//以當前過期的slot位置開始,做一次探測式清理工作。i = expungeStaleEntry(i);}/** 表示循環次數。*/ } while ( (n >>>= 1) != 0);return removed;}5.set()方法流程圖
6.rehash()方法
只有當前table元素個數大于等于擴容閾值并且在清理完table內部的所有過期的entry后,元素個數還大于等于閾值的3/4,這時才會觸發擴容。
//--------------------ThreadLocalMap.rehash()---------------------------------private void rehash() {//這個方法執行完畢后,當前散列表內部所有過期的數據,都會被干掉。expungeStaleEntries();//條件成立,說明清理完所有的過期entry后,size數量仍然達到了擴容閾值的 3/4//才會去做一次resize()擴容。if (size >= threshold - threshold / 4)resize();}//------------------------ThreadLocalMap.resize()----------------------------- private void resize() {//擴容,變為原長度的2倍Entry[] oldTab = table;int oldLen = oldTab.length;int newLen = oldLen * 2; // 新長度 = 原長度 * 2;//創建一個新的table,Entry[] newTab = new Entry[newLen];//表示新表中的元素個數int count = 0;//遍歷原表中的每一個slot,將原表中的數據遷移到新表。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);//遍歷找空位置(找到距離目標位置最近的一個slot)while (newTab[h] != null)h = nextIndex(h, newLen);//放到新位置newTab[h] = e;count++;}}}setThreshold(newLen); //設置新的擴容閾值size = count; // 將count賦值給size table = newTab; //將新表賦值給table。}//----------------------ThreadLcoalMap.remove()-------------------------------private void remove(ThreadLocal<?> key) {Entry[] tab = table;int len = tab.length;//根據key獲取索引位置int i = key.threadLocalHashCode & (len - 1);//遍歷for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {//找到指定keyif (e.get() == key) { /** entry是弱引用,調用clear()方法會將內部關聯的threadLocal置為 * NULL */e.clear();//清理當前位置 將entry內部的value以及entry干掉。expungeStaleEntry(i);return;}}}7.三大清理方法流程圖
expungeStaleEntry運行流程
cleanSomeSlots運行流程
replaceStaleEntry運行流程
總結
以上是生活随笔為你收集整理的ThreadLocal源码解析2.ThreadLocalMap的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 2019中国五大新兴制造业迁徙路径及产业
- 下一篇: hcfax2e伺服驱动器说明书_SD伺服
