摘要:計算哈希后確定的槽內是沒有表示沒有哈希沖突,此時一個放入槽內。下一個位置繼續找如果循環結束了,表示哈希位往后尋找的都不是當前的,返回。新的位置上有表示哈希沖突,則繼續向后尋找。
每個Thread中都持有一個ThreadLocalMap的實例,ThreadLocalMap是ThreadLocal的內部類。當Thread中沒有ThreadLocalMap則需要先實例化ThreadLocalMap.
public class Thread implements Runnable { ThreadLocal.ThreadLocalMap threadLocals = null;//該對象是ThreadLocal中的內部類ThreadLocalMap}public class ThreadLocal{ //計算出來的hash值用它來確定Entry存放到哪個哈希槽 private final int threadLocalHashCode = nextHashCode(); //這是個固定值 private static final int HASH_INCREMENT = 0x61c88647; //這個默認值是0,但new ThreadLocal后斷點看到的值不是0,這是因為這是一個靜態成員,在我們自己創建ThreadLocal前,main方法會先加載ThreadLocal給這個賦值了。 private static AtomicInteger nextHashCode = new AtomicInteger(); //每次調用該方法都會在原有的nextHashCode值上加上0x61c88647 private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); } //設置值 public void set(T value) { Thread t = Thread.currentThread();//獲取當前線程。 ThreadLocalMap map = getMap(t);//獲取當前線程的成員變量ThreadLocal.ThreadLocalMap threadLocals if (map != null) map.set(this, value);//如果當前線程中的ThreadLocalMap已經實例化則set else createMap(t, value);//如果當前線程中的ThreadLocalMap沒有實例化則實例化。 } //在這走實例化ThreadLocalMap void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }}
ThreadLocalMap里最重要的屬性是Entry[],這個數組的初始長度是16,擴容閾值是size*2/3,Entry是ThreadLocalMap的內部類,Entry繼承了弱引用。Entry里的key是ThreadLocal,value是設置的值。如果ThreadLocal棧引用結束了,在發生GC時雖然Entry還持有ThreadLocal的引用,這個ThreadLocal也會被垃圾回收,所以ThreadLocalMap常常伴隨著擴容,清理操作。
static class ThreadLocalMap { //繼承WeakReference很重要,WeakReferences是弱引用,在每次GC后都會回收弱引用對象里的引用值(若通過可達性分析查到引用值沒有其他可達的Root,則會回收) //這個Entry就構成了唯一的key,也就是ThreadLocal。value是ThreadLocal.set(parameter)的參數 static class Entry extends WeakReference> { Object value; Entry(ThreadLocal> k, Object v) { super(k);//最終傳遞給了Reference中的referent value = v; } } //ThreadLocalMap中的容器,一個線程持有一個ThreadLocalMap就相當于持有了一個Entry數組 private Entry[] table; //數組的初始容量 private static final int INITIAL_CAPACITY = 16; ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY];//實例化數組 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//確定數組的位置 //初始化ThreadLocalMap不會出現hash沖突。 table[i] = new Entry(firstKey, firstValue); //已有元素++ size = 1; //計算擴容閾值 setThreshold(INITIAL_CAPACITY); } private void setThreshold(int len) { //初始化的容量第一次擴容的閾值是10,也就是說在數組的size是10的情況下就會觸發擴容。 threshold = len * 2 / 3; } }
}
ThreadLocal的set方法是使用ThreadLocalMap的set方法。他分為四種情況。1 計算哈希后確定的槽內是null沒有Entry表示沒有哈希沖突,此時new一個Entry放入槽內。 2 計算哈希后確定的槽內有Entry但是槽內的Entry的key和當前的ThreadLocal相同則直接替換value。
3 計算哈希后確定的槽內有Entry但是key和當前ThreadLocal并不是同一個,則表示哈希沖突,此時順著數組往右尋找,直到碰到有Entry但是沒有key的槽,這表示這個槽內曾經有過ThreadLocal但是被GC掉了,此時這個槽是個廢槽,可以替換掉Entry。 4 哈希沖突后向右
并沒有找到被GC的槽,此時只能是找到距離最近的一個槽內沒有Entry的,創建一個Entry存入。
static class ThreadLocalMap { //順著當前下標往后查詢。如果查詢到了數組末尾則返回0號下標 private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); } //順著當前下標往前查詢。如果已經是0則返回數組末尾下標 private static int prevIndex(int i, int len) { return ((i - 1 >= 0) ? i - 1 : len - 1); } private void set(ThreadLocal> key, Object value) { //拿到數組 Entry[] tab = table; //數組長度 int len = tab.length; //hash&length-1 效果類似hash%length int i = key.threadLocalHashCode & (len-1); //在這就要處理hash沖突了。如果hash值不沖突,那么算出來的index位置的Entry肯定是null.那么不會進入循環。 //如果進入了循環,有沒有可能兩個if都不滿足,有可能。這表示hash值沖突了,但是不是同一個ThreadLocal,并且hash值相同的槽內的ThreadLocal沒有被GC。 //那么只能是一直找到Entry是null的位置,然后跳出循環。 for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) { ThreadLocal> k = e.get(); //如果是第一次循環到這里進去了,表示是同一個ThreadLocal多次設置值。則直接替換值。情況2 if (k == key) { e.value = value; return; } //如果ThreadLocal為null則表示發生了GC把弱引用ThreadLocal清理了。 //需要將當前set的key和value放入這個廢掉的槽內,并且看看有沒有需要清理的槽。情況3 if (k == null) { replaceStaleEntry(key, value, i); return; } } //沒有進入循環,或者從循環跳出了。如果沒有進入循環則i就是hash&length-1的位置表示當前算出來的hash值沒有沖突,也是第一次使用。情況1 //如果是循環跳出來的,則這個i就是hash&length-1.算出來的位置向后移動循環次數的位置。表示hash沖突了,并且沖突后的槽往后也都沒有被GC //只能是往后順延找別的可用槽。總之會找到一個在數組內Entry為空的位置。創建Entry放進數組。情況4 tab[i] = new Entry(key, value); //已有元素++ int sz = ++size;
//如果沒有清理槽,并且當前長度已經大于等于了閾值則擴容 if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } //走到這個方法表示通過hash&length-1的位置上的Entry中的key是null或者是哈希沖突后,往數組后查詢發現有Entry中的key是null private void replaceStaleEntry(ThreadLocal> key, Object value,int staleSlot) { //數組 Entry[] tab = table; //數組長度 int len = tab.length; Entry e; //Entry為null的哈希槽 int slotToExpunge = staleSlot; //從Entry為null的哈希槽位置向前找,一直找到Entry為null停止 for (int i = prevIndex(staleSlot, len);(e = tab[i]) != null;i = prevIndex(i, len)){ //在向前尋找的過程中標記Entry中key為null的下標 if (e.get() == null) slotToExpunge = i; } //從Entry為null的哈希槽位置向后找,一直找到Entry為null停止 for (int i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) { //循環中的Entry中的key ThreadLocal> k = e.get(); if (k == key) { //如果key相同則替換value e.value = value; //將Entry中ThreadLocal為null的賦值給當前槽中 tab[i] = tab[staleSlot]; //在將Entry賦值給原來ThreadLocal為null的槽中。 //這兩行操作相當于把槽里的內容互換了,達到的效果是前邊的槽中的Entry有key,循環中的也就是后邊的沒有key tab[staleSlot] = e; //如果列表向左查詢沒有發現Entry中key有null的。則將當前循環中的槽的位置賦值。 //因為上兩步操作已經把當前槽變成了key為null的槽,所以此處記錄的位置就是key是null的位置 //如果向左查詢有Entry里是null值那就表示這個區間內還有更左邊有key是null的 if (slotToExpunge == staleSlot){ slotToExpunge = i; } //清理槽 cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; } //當前循環中的槽也是被GC過的。并且向左查詢沒有發現Entry為null的,就記錄當前槽的位置。 if (k == null && slotToExpunge == staleSlot){ slotToExpunge = i; } } //出循環只有一種情況,key為null的Entry下標往后尋找沒有發現與當前ThreadLocal相同的key。 //此時需要將原來Entry的value職位null。此操作用來釋放內存。 tab[staleSlot].value = null; //創建一個新的Entry其中key是當前ThreadLocal,value是set的參數。將它放到被GC的位置。 tab[staleSlot] = new Entry(key, value); //如果向左查詢有Entry中key是null的slotToExpunge就是在左邊確定的 //如果向左查詢沒有Entry中key是null的,而向右查詢有Entry中key是null的slotToExpunge就是右邊確定的。 //如果兩邊都沒有的情況表示當前區間內只有staleSlot一個為Entry是null的而這種情況下直接重新覆蓋了Entry。不需要清理。條件不成立。 if (slotToExpunge != staleSlot){ cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); } } }
expungeStaleEntry方法就是將廢槽清空,然后將哈希沖突的槽重新分配位置,因為哈希沖突后是從哈希位向后移動尋找Entry是null的槽放入的,此后這些沖突的槽可能有被清理的,所以重新分配位置,方法的返回值是Entry為null的位置,cleanSomeSlots方法從這個位置
繼續尋找有沒有廢槽,如果有就清理。
static class ThreadLocalMap { //接收的參數是槽里沒有Entry的槽和當前數組的長度 private boolean cleanSomeSlots(int i, int n) { boolean removed = false; Entry[] tab = table; int len = tab.length; do { //找到下一個槽的位置 i = nextIndex(i, len); //獲取槽內的Entry Entry e = tab[i]; //如果槽內有Entry,并且Entry的key是null,表示這是個廢槽。 if (e != null && e.get() == null) { n = len; //有廢槽肯定要清理的。 removed = true; //方法返回下一個槽內沒有Entry的槽下標 i = expungeStaleEntry(i); } } while ( (n >>>= 1) != 0);//這個操作相當于折半除2的操作。10,5,2,0, return removed; } //接收的參數是槽下標內有Entry,但是Entry的key被GC了。 private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; //將槽清空 tab[staleSlot].value = null; tab[staleSlot] = null; //Entry[]-- size--; Entry e; int i; //循環的開始是廢槽的下一個,終止條件是下一個槽有Entry for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) { //拿到槽內的ThreadLocal ThreadLocal> k = e.get(); //如果槽內的key也是null則表示這也是個廢槽,則也需要做清空操作。 if (k == null) { e.value = null; tab[i] = null; size--; } else { //如果槽內有的Entry有key,則通過hash值算出槽的位置。 int h = k.threadLocalHashCode & (len - 1); //如果算出的槽位置不是當前的位置則表示這個key曾經哈希沖突了,所以位置并不是哈希位。 if (h != i) { //將這個槽清空 tab[i] = null; //從計算的哈希位開始循環,找到Entry為null的槽,將剛剛清空槽里的Entry重新安置。 while (tab[h] != null){ h = nextIndex(h, len); } //這一步的操作的意義在于,如果循環中有if條件滿足的,這代表當前i這個位置之前有可用的槽,那就從哈希位開始往后找,找到空槽,重新安置這個Entry。 tab[h] = e; } } } return i;//入參staleSlot是一個廢槽,返回的i則是一個Entry為null的槽。 } }
get方法通過當前ThreadLocal獲取Entry[]中對應的Entry,如果ThreadLocalMap未實例化則實例化并返回null,通過哈希位找到了就返回,哈希位上的不是當前ThreadLocal則表示哈希沖突,繼續在數組后尋找,如果途中發現有廢槽則清理,如果最終沒有找到則返回null。
public class ThreadLocal{ public T get() { //獲取當前線程 Thread t = Thread.currentThread(); //獲取線程內的ThreadLocalMap ThreadLocalMap map = getMap(t); //如果ThreadLocalMap已經實例化 if (map != null) { //通過ThreadLocal這個key到數組中找到Entry,是有可能找不到返回null的 ThreadLocalMap.Entry e = map.getEntry(this); //如果找到了,返回Entry中的value if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //走到這里兩種情況,1 ThreadLocalMap沒有實例化,則實例化 2 從Entry[]沒有找到對應ThreadLocal的Entry return setInitialValue(); } //這個方法和set差不多,但是它可以返回null。 private T setInitialValue() { //如果現在使用的就是ThreadLocal則一定返回null. T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } //這個方法只能子類重寫,意味著可以給ThreadLocal賦默認值。 protected T initialValue() { return null; } }static class ThreadLocalMap { static class ThreadLocalMap { //通過ThreadLocal找Entry private Entry getEntry(ThreadLocal> key) { //計算哈希位 int i = key.threadLocalHashCode & (table.length - 1); //查看哈希位上的Entry Entry e = table[i]; //如果Entry不是null或者Entry的key就是當前的ThreadLocal則找到了返回Entry if (e != null && e.get() == key){ return e; } else{ //如果從哈希位沒有找到Entry或者Entry中的key不是當前ThreadLocal return getEntryAfterMiss(key, i, e); } } //接收的參數是當前ThreadLocal,計算的哈希位,和這個哈希位上的Entry private Entry getEntryAfterMiss(ThreadLocal> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; //從哈希位上開始循環尋找 while (e != null) { ThreadLocal> k = e.get(); //如果找到了key相同的則返回 if (k == key){ return e; } //如果當前槽內的key是null則要被清理 if (k == null){ expungeStaleEntry(i); }else{ //如果槽內的key有值則繼續尋找。直到Entry位null停止。 i = nextIndex(i, len); } //下一個位置繼續找 e = tab[i]; } //如果循環結束了,表示哈希位往后尋找的key都不是當前的ThreadLocal,返回null。 return null; } }
當數組內的元素到達閾值后觸發擴容,擴容操作進行前會遍歷數組進行清理。如果清理后仍然達到閾值則二倍擴容,循環擴容前的數組,根據新數組的長度重新計算哈希值,如果哈希槽內沒有元素則放入,如果有則線性查詢可用槽放入。然后用新的數組替換老的數組。
?
static class ThreadLocalMap { //擴容 private void rehash() { //清理一遍槽 expungeStaleEntries(); //大于閾值擴容 if (size >= threshold - threshold / 4) resize(); } //全部清理 private void expungeStaleEntries() { Entry[] tab = table; int len = tab.length; //遍歷數組清理 for (int j = 0; j < len; j++) { Entry e = tab[j]; //發現廢槽就清理 if (e != null && e.get() == null){ expungeStaleEntry(j); } } } private void resize() { //擴容前的數組 Entry[] oldTab = table; //擴容前數組的長度 int oldLen = oldTab.length; //二倍擴容 int newLen = oldLen * 2; //創建新的數組 Entry[] newTab = new Entry[newLen]; int count = 0; //遍歷擴容前的數組 for (int j = 0; j < oldLen; ++j) { Entry e = oldTab[j]; //如果Entry不是null if (e != null) { //獲取key ThreadLocal> k = e.get(); if (k == null) { //key是null清理 e.value = null; } else { //根據哈希值算出來在新的數組中的位置。 int h = k.threadLocalHashCode & (newLen - 1); //新的位置上有Entry表示哈希沖突,則繼續向后尋找。 while (newTab[h] != null){ h = nextIndex(h, newLen); } //找到一個Entry為null的位置存放Entry。 newTab[h] = e; count++; } } } //設置新的閾值 setThreshold(newLen); //新數組內元素的總個數 size = count; //替換數組 table = newTab; } }
clear方法就是把ThreadLocal從Entry中刪除,然后刪除Entry。這樣Entry就沒有了引用會被GC。如果不使用clear,那么就算是ThreadLocal棧內存釋放了,這個對象還是存在于Thread里的ThreadLocalMap里的Entry[]數組中,除非遇到GC否則永遠存在。手動清理的作用就在于不用等待GC自己把Entry清理。
public class ThreadLocal{ //通過ThreadLocalMap的remove方法釋放內存 public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } }static class ThreadLocalMap { //通過當前ThreadLocal刪除 private void remove(ThreadLocal> key) { Entry[] tab = table; int len = tab.length; //計算哈希位 int i = key.threadLocalHashCode & (len-1); //循環找匹配的key for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { //調用Refereence的clear把key清空 e.clear(); //再次清理槽。 expungeStaleEntry(i); return; } } }}public abstract class Reference { private T referent;//這個就是ThreadLocal對象 public void clear() { this.referent = null; } }
?
?
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/124534.html
摘要:雖然類名中帶有字樣,但是實際上并不是接口的子類。是弱連接接口,這意味著如果僅有指向某一類,其任然有可能被回收掉。這里使用弱連接的意義,是為了防止業務代碼中置空對象,但是由于存在連接可達,所以仍然無法回收掉該對象的情況發生。 零 前期準備 0 FBI WARNING 文章異常啰嗦且繞彎。 1 版本 JDK 版本 : OpenJDK 11.0.1 IDE : idea 2018.3 2 T...
摘要:下來我們來看中的方法。從中可以看到真正保存的是在中,接著看看的源碼。數組的初始長度為,最多可保存一旦超過就進行擴容增加一倍。而內部利用數組來保存和值的,數組的索引就是的哈希值數組的長度。 在Android-27中查看源碼: 在Looper源碼中,我們看到通過ThreadLocal的set方法來保存Looper,通過get方法來取出Looper。下來我們來看ThreadLocal中的se...
摘要:在深入理解中的變量上中我們看到的引入,使得可以很方便地在多線程環境中使用局部變量。特別需要注意的是,基類的并不會屏蔽派生類中的創建。到此,整個源碼核心部分已經理解的差不多了,只剩下用來執行清除工作。 在 深入理解Python中的ThreadLocal變量(上) 中我們看到 ThreadLocal 的引入,使得可以很方便地在多線程環境中使用局部變量。如此美妙的功能到底是怎樣實現的?如果你...
摘要:返回索引位置的值。因為依賴于靜態成員變量的關系,所以它的肯定唯一獲取當前線程。位置還沒有初始化第一次這個,直接將放到的位置。在線程池模式下,生命周期伴隨著線程一直存在,可能出現內存泄漏的情況,最好手動調用方法。 本文原創地址,:jsbintask的博客(食用效果最佳),轉載請注明出處! 前言 ThreadLocal是jdk中一個非常重要的工具,它可以控制堆內存中的對象只能被指定線程訪問,如...
閱讀 2569·2021-11-23 09:51
閱讀 2481·2021-09-30 09:48
閱讀 1075·2021-09-10 10:51
閱讀 2213·2021-08-12 13:22
閱讀 3567·2021-08-11 10:24
閱讀 2166·2019-08-30 15:55
閱讀 645·2019-08-30 14:05
閱讀 3211·2019-08-30 13:03