摘要:什么是,簡(jiǎn)單翻譯過(guò)來(lái)就是本地線程,但是直接這么翻譯很難理解的作用,如果換一種說(shuō)法,可以稱為線程本地存儲(chǔ)。魔數(shù)的選取和斐波那契散列有關(guān),對(duì)應(yīng)的十進(jìn)制為。而斐波那契散列的乘數(shù)可以用如果把這個(gè)值給轉(zhuǎn)為帶符號(hào)的,則會(huì)得到。
什么是ThreadLocal
ThreadLocal,簡(jiǎn)單翻譯過(guò)來(lái)就是本地線程,但是直接這么翻譯很難理解ThreadLocal的作用,如果換一種說(shuō)法,可以稱為線程本地存儲(chǔ)。簡(jiǎn)單來(lái)說(shuō),就是ThreadLocal為共享變量在每個(gè)線程中都創(chuàng)建一個(gè)副本,每個(gè)線程可以訪問(wèn)自己內(nèi)部的副本變量。這樣做的好處是可以保證共享變量在多線程環(huán)境下訪問(wèn)的線程安全性
ThreadLocal的使用 沒(méi)有使用ThreadLocal時(shí)通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)演示一下ThreadLocal的作用,這段代碼是定義了一個(gè)靜態(tài)的成員變量num,然后通過(guò)構(gòu)造5個(gè)線程對(duì)這個(gè)num做遞增
public class ThreadLocalDemo { private static Integer num=0; public static void main(String[] args) { Thread[] threads=new Thread[5]; for(int i=0;i<5;i++){ threads[i]=new Thread(()->{ num+=5; System.out.println(Thread.currentThread().getName()+" : "+num); },"Thread-"+i); } for(Thread thread:threads){ thread.start(); } } }
運(yùn)行結(jié)果
Thread-0 : 5 Thread-1 : 10 Thread-2 : 15 Thread-3 : 20 Thread-4 : 25
每個(gè)線程都會(huì)對(duì)這個(gè)成員變量做遞增,如果線程的執(zhí)行順序不確定,那么意味著每個(gè)線程獲得的結(jié)果也是不一樣的。
使用了ThreadLocal以后通過(guò)ThreadLocal對(duì)上面的代碼做一個(gè)改動(dòng)
public class ThreadLocalDemo { private static final ThreadLocallocal=new ThreadLocal (){ protected Integer initialValue(){ return 0; //通過(guò)initialValue方法設(shè)置默認(rèn)值 } }; public static void main(String[] args) { Thread[] threads=new Thread[5]; for(int i=0;i<5;i++){ threads[i]=new Thread(()->{ int num=local.get().intValue(); num+=5; System.out.println(Thread.currentThread().getName()+" : "+num); },"Thread-"+i); } for(Thread thread:threads){ thread.start(); } } }
運(yùn)行結(jié)果
Thread-0 : 5 Thread-4 : 5 Thread-2 : 5 Thread-1 : 5 Thread-3 : 5
從結(jié)果可以看到,每個(gè)線程的值都是5,意味著各個(gè)線程之間都是獨(dú)立的變量副本,彼此不相互影響.
ThreadLocal會(huì)給定一個(gè)初始值,也就是initialValue()方法,而每個(gè)線程都會(huì)從ThreadLocal中獲得這個(gè)初始化的值的副本,這樣可以使得每個(gè)線程都擁有一個(gè)副本拷貝
看到這里,估計(jì)有很多人都會(huì)和我一樣有一些疑問(wèn)
每個(gè)線程的變量副本是怎么存儲(chǔ)的?
ThreadLocal是如何實(shí)現(xiàn)多線程場(chǎng)景下的共享變量副本隔離?
帶著疑問(wèn),來(lái)看一下ThreadLocal這個(gè)類(lèi)的定義(默認(rèn)情況下,JDK的源碼都是基于1.8版本)
從ThreadLocal的方法定義來(lái)看,還是挺簡(jiǎn)單的。就幾個(gè)方法
get: 獲取ThreadLocal中當(dāng)前線程對(duì)應(yīng)的線程局部變量
set:設(shè)置當(dāng)前線程的線程局部變量的值
remove:將當(dāng)前線程局部變量的值刪除
另外,還有一個(gè)initialValue()方法,在前面的代碼中有演示,作用是返回當(dāng)前線程局部變量的初始值,這個(gè)方法是一個(gè)protected方法,主要是在構(gòu)造ThreadLocal時(shí)用于設(shè)置默認(rèn)的初始值
set方法的實(shí)現(xiàn)set方法是設(shè)置一個(gè)線程的局部變量的值,相當(dāng)于當(dāng)前線程通過(guò)set設(shè)置的局部變量的值,只對(duì)當(dāng)前線程可見(jiàn)。
public void set(T value) { Thread t = Thread.currentThread();//獲取當(dāng)前執(zhí)行的線程 ThreadLocalMap map = getMap(t); //獲得當(dāng)前線程的ThreadLocalMap實(shí)例 if (map != null)//如果map不為空,說(shuō)明當(dāng)前線程已經(jīng)有了一個(gè)ThreadLocalMap實(shí)例 map.set(this, value);//直接將當(dāng)前value設(shè)置到ThreadLocalMap中 else createMap(t, value); //說(shuō)明當(dāng)前線程是第一次使用線程本地變量,構(gòu)造map }
Thread.currentThread 獲取當(dāng)前執(zhí)行的線程
getMap(t) ,根據(jù)當(dāng)前線程得到當(dāng)前線程的ThreadLocalMap對(duì)象,這個(gè)對(duì)象具體是做什么的?稍后分析
如果map不為空,說(shuō)明當(dāng)前線程已經(jīng)構(gòu)造過(guò)ThreadLocalMap,直接將值存儲(chǔ)到map中
如果map為空,說(shuō)明是第一次使用,調(diào)用createMap構(gòu)造
ThreadLocalMap是什么?我們來(lái)分析一下這句話,ThreadLocalMap map=getMap(t)獲得一個(gè)ThreadLocalMap對(duì)象,那這個(gè)對(duì)象是干嘛的呢?
其實(shí)不用分析,基本上也能猜測(cè)出來(lái),Map是一個(gè)集合,集合用來(lái)存儲(chǔ)數(shù)據(jù),那么在ThreadLocal中,應(yīng)該就是用來(lái)存儲(chǔ)線程的局部變量的。ThreadLocalMap這個(gè)類(lèi)很關(guān)鍵。
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
t.threadLocals實(shí)際上就是訪問(wèn)Thread類(lèi)中的ThreadLocalMap這個(gè)成員變量
public class Thread implements Runnable { /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; ... }
從上面的代碼發(fā)現(xiàn)每一個(gè)線程都有自己多帶帶的ThreadLocalMap實(shí)例,而對(duì)應(yīng)這個(gè)線程的所有本地變量都會(huì)保存到這個(gè)map內(nèi)
ThreadLocalMap是在哪里構(gòu)造?在set方法中,有一行代碼createmap(t,value);,這個(gè)方法就是用來(lái)構(gòu)造ThreadLocalMap,從傳入的參數(shù)來(lái)看,它的實(shí)現(xiàn)邏輯基本也能猜出出幾分吧
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
Thread t 是通過(guò)Thread.currentThread()來(lái)獲取的表示當(dāng)前線程,然后直接通過(guò)new ThreadLocalMap將當(dāng)前線程中的threadLocals做了初始化
ThreadLocalMap是一個(gè)靜態(tài)內(nèi)部類(lèi),內(nèi)部定義了一個(gè)Entry對(duì)象用來(lái)真正存儲(chǔ)數(shù)據(jù)
static class ThreadLocalMap { static class Entry extends WeakReference> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal> k, Object v) { super(k); value = v; } } ThreadLocalMap(ThreadLocal> firstKey, Object firstValue) { //構(gòu)造一個(gè)Entry數(shù)組,并設(shè)置初始大小 table = new Entry[INITIAL_CAPACITY]; //計(jì)算Entry數(shù)據(jù)下標(biāo) int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); //將`firstValue`存入到指定的table下標(biāo)中 table[i] = new Entry(firstKey, firstValue); size = 1;//設(shè)置節(jié)點(diǎn)長(zhǎng)度為1 setThreshold(INITIAL_CAPACITY); //設(shè)置擴(kuò)容的閾值 } //...省略部分代碼 }
分析到這里,基本知道了ThreadLocalMap長(zhǎng)啥樣了,也知道它是如何構(gòu)造的?那么我看到這里的時(shí)候仍然有疑問(wèn)
Entry集成了WeakReference,這個(gè)表示什么意思?
在構(gòu)造ThreadLocalMap的時(shí)候new ThreadLocalMap(this, firstValue);,key其實(shí)是this,this表示當(dāng)前對(duì)象的引用,在當(dāng)前的案例中,this指的是ThreadLocal
weakReference表示弱引用,在Java中有四種引用類(lèi)型,強(qiáng)引用、弱引用、軟引用、虛引用。
使用弱引用的對(duì)象,不會(huì)阻止它所指向的對(duì)象被垃圾回收器回收。
在Java語(yǔ)言中, 當(dāng)一個(gè)對(duì)象o被創(chuàng)建時(shí), 它被放在Heap里. 當(dāng)GC運(yùn)行的時(shí)候, 如果發(fā)現(xiàn)沒(méi)有任何引用指向o, o就會(huì)被回收以騰出內(nèi)存空間. 也就是說(shuō), 一個(gè)對(duì)象被回收, 必須滿足兩個(gè)條件:
沒(méi)有任何引用指向它
GC被運(yùn)行.
這段代碼中,構(gòu)造了兩個(gè)對(duì)象a,b,a是對(duì)象DemoA的引用,b是對(duì)象DemoB的引用,對(duì)象DemoB同時(shí)還依賴對(duì)象DemoA,那么這個(gè)時(shí)候我們認(rèn)為從對(duì)象DemoB是可以到達(dá)對(duì)象DemoA的。這種稱為強(qiáng)可達(dá)(strongly reachable)
DemoA a=new DemoA(); DemoB b=new DemoB(a);
如果我們?cè)黾右恍写a來(lái)將a對(duì)象的引用設(shè)置為null,當(dāng)一個(gè)對(duì)象不再被其他對(duì)象引用的時(shí)候,是會(huì)被GC回收的,但是對(duì)于這個(gè)場(chǎng)景來(lái)說(shuō),即時(shí)是a=null,也不可能被回收,因?yàn)镈emoB依賴DemoA,這個(gè)時(shí)候是可能造成內(nèi)存泄漏的
DemoA a=new DemoA(); DemoB b=new DemoB(a); a=null;
通過(guò)弱引用,有兩個(gè)方法可以避免這樣的問(wèn)題
//方法1 DemoA a=new DemoA(); DemoB b=new DemoB(a); a=null; b=null; //方法2 DemoA a=new DemoA(); WeakReference b=new WeakReference(a); a=null;
對(duì)于方法2來(lái)說(shuō),DemoA只是被弱引用依賴,假設(shè)垃圾收集器在某個(gè)時(shí)間點(diǎn)決定一個(gè)對(duì)象是弱可達(dá)的(weakly reachable)(也就是說(shuō)當(dāng)前指向它的全都是弱引用),這時(shí)垃圾收集器會(huì)清除所有指向該對(duì)象的弱引用,然后把這個(gè)弱可達(dá)對(duì)象標(biāo)記為可終結(jié)(finalizable)的,這樣它隨后就會(huì)被回收。
試想一下如果這里沒(méi)有使用弱引用,意味著ThreadLocal的生命周期和線程是強(qiáng)綁定,只要線程沒(méi)有銷(xiāo)毀,那么ThreadLocal一直無(wú)法回收。而使用弱引用以后,當(dāng)ThreadLocal被回收時(shí),由于Entry的key是弱引用,不會(huì)影響ThreadLocal的回收防止內(nèi)存泄漏,同時(shí),在后續(xù)的源碼分析中會(huì)看到,ThreadLocalMap本身的垃圾清理會(huì)用到這一個(gè)好處,方便對(duì)無(wú)效的Entry進(jìn)行回收解惑ThreadLocalMap以this作為key
在構(gòu)造ThreadLocalMap時(shí),使用this作為key來(lái)存儲(chǔ),那么對(duì)于同一個(gè)ThreadLocal對(duì)象,如果同一個(gè)Thread中存儲(chǔ)了多個(gè)值,是如何來(lái)區(qū)分存儲(chǔ)的呢?
答案就在firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1)
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); }
關(guān)鍵點(diǎn)就在threadLocalHashCode,它相當(dāng)于一個(gè)ThreadLocal的ID,實(shí)現(xiàn)的邏輯如下
private final int threadLocalHashCode = nextHashCode(); private static AtomicInteger nextHashCode = new AtomicInteger(); private static final int HASH_INCREMENT = 0x61c88647; private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT); }
這里用到了一個(gè)非常完美的散列算法,可以簡(jiǎn)單理解為,對(duì)于同一個(gè)ThreadLocal下的多個(gè)線程來(lái)說(shuō),當(dāng)任意線程調(diào)用set方法存入一個(gè)數(shù)據(jù)到Entry中的時(shí)候,其實(shí)會(huì)根據(jù)threadLocalHashCode生成一個(gè)唯一的id標(biāo)識(shí)對(duì)應(yīng)這個(gè)數(shù)據(jù),存儲(chǔ)在Entry數(shù)據(jù)下標(biāo)中。
threadLocalHashCode是通過(guò)nextHashCode.getAndAdd(HASH_INCREMENT)來(lái)實(shí)現(xiàn)的
i*HASH_INCREMENT+HASH_INCREMENT,每次新增一個(gè)元素(ThreadLocal)到Entry[],都會(huì)自增0x61c88647,目的為了讓哈希碼能均勻的分布在2的N次方的數(shù)組里
Entry[i]= hashCode & (length-1)
魔數(shù)0x61c88647從上面的分析可以看出,它是在上一個(gè)被構(gòu)造出的ThreadLocal的threadLocalHashCode的基礎(chǔ)上加上一個(gè)魔數(shù)0x61c88647。我們來(lái)做一個(gè)實(shí)驗(yàn),看看這個(gè)散列算法的運(yùn)算結(jié)果
private static final int HASH_INCREMENT = 0x61c88647; public static void main(String[] args) { magicHash(16); //初始大小16 magicHash(32); //擴(kuò)容一倍 } private static void magicHash(int size){ int hashCode = 0; for(int i=0;i輸出結(jié)果
7 14 5 12 3 10 1 8 15 6 13 4 11 2 9 0 7 14 21 28 3 10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26 1 8 15 22 29 4 11 18 25 0根據(jù)運(yùn)行結(jié)果,這個(gè)算法在長(zhǎng)度為2的N次方的數(shù)組上,確實(shí)可以完美散列,沒(méi)有任何沖突, 是不是很神奇。
魔數(shù)0x61c88647的選取和斐波那契散列有關(guān),0x61c88647對(duì)應(yīng)的十進(jìn)制為1640531527。而斐波那契散列的乘數(shù)可以用(long) ((1L << 31) * (Math.sqrt(5) - 1)); 如果把這個(gè)值給轉(zhuǎn)為帶符號(hào)的int,則會(huì)得到-1640531527。也就是說(shuō)
(long) ((1L << 31) * (Math.sqrt(5) - 1));得到的結(jié)果就是1640531527,也就是魔數(shù)0x61c88647//(根號(hào)5-1)*2的31次方=(根號(hào)5-1)/2 *2的32次方=黃金分割數(shù)*2的32次方 long l1 = (long) ((1L << 31) * (Math.sqrt(5) - 1)); System.out.println("32位無(wú)符號(hào)整數(shù): " + l1); int i1 = (int) l1; System.out.println("32位有符號(hào)整數(shù): " + i1);總結(jié),我們用0x61c88647作為魔數(shù)累加為每個(gè)ThreadLocal分配各自的ID也就是threadLocalHashCode再與2的冪取模,得到的結(jié)果分布很均勻。圖形分析為了更直觀的體現(xiàn)set方法的實(shí)現(xiàn),通過(guò)一個(gè)圖形表示如下
set剩余源碼分析前面分析了set方法第一次初始化ThreadLocalMap的過(guò)程,也對(duì)ThreadLocalMap的結(jié)構(gòu)有了一個(gè)全面的了解。那么接下來(lái)看一下map不為空時(shí)的執(zhí)行邏輯
private void set(ThreadLocal> key, Object value) { Entry[] tab = table; int len = tab.length; // 根據(jù)哈希碼和數(shù)組長(zhǎng)度求元素放置的位置,即數(shù)組下標(biāo) int i = key.threadLocalHashCode & (len-1); //從i開(kāi)始往后一直遍歷到數(shù)組最后一個(gè)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; } //如果key為null,用新key、value覆蓋,同時(shí)清理歷史key=null的陳舊數(shù)據(jù) if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; //如果超過(guò)閥值,就需要擴(kuò)容了 if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }主要邏輯
根據(jù)key的散列哈希計(jì)算Entry的數(shù)組下標(biāo)
通過(guò)線性探索探測(cè)從i開(kāi)始往后一直遍歷到數(shù)組的最后一個(gè)Entry
如果map中的key和傳入的key相等,表示該數(shù)據(jù)已經(jīng)存在,直接覆蓋
如果map中的key為空,則用新的key、value覆蓋,并清理key=null的數(shù)據(jù)
rehash擴(kuò)容
replaceStaleEntry由于Entry的key為弱引用,如果key為空,說(shuō)明ThreadLocal這個(gè)對(duì)象被GC回收了。
replaceStaleEntry的作用就是把陳舊的Entry進(jìn)行替換private void replaceStaleEntry(ThreadLocal> key, Object value, int staleSlot) { Entry[] tab = table; int len = tab.length; Entry e; //向前掃描,查找最前一個(gè)無(wú)效的slot int slotToExpunge = staleSlot; for (int i = prevIndex(staleSlot, len); (e = tab[i]) != null; i = prevIndex(i, len)) if (e.get() == null) //通過(guò)循環(huán)遍歷,可以定位到最前面一個(gè)無(wú)效的slot slotToExpunge = i; //從i開(kāi)始往后一直遍歷到數(shù)組最后一個(gè)Entry(線性探索) for (int i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal> k = e.get(); //找到匹配的key以后 if (k == key) { e.value = value;//更新對(duì)應(yīng)slot的value值 //與無(wú)效的sloat進(jìn)行交換 tab[i] = tab[staleSlot]; tab[staleSlot] = e; //如果最早的一個(gè)無(wú)效的slot和當(dāng)前的staleSlot相等,則從i作為清理的起點(diǎn) if (slotToExpunge == staleSlot) slotToExpunge = i; //從slotToExpunge開(kāi)始做一次連續(xù)的清理 cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); return; } //如果當(dāng)前的slot已經(jīng)無(wú)效,并且向前掃描過(guò)程中沒(méi)有無(wú)效slot,則更新slotToExpunge為當(dāng)前位置 if (k == null && slotToExpunge == staleSlot) slotToExpunge = i; } //如果key對(duì)應(yīng)的value在entry中不存在,則直接放一個(gè)新的entry tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); //如果有任何一個(gè)無(wú)效的slot,則做一次清理 if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }cleanSomeSlots這個(gè)函數(shù)有兩處地方會(huì)被調(diào)用,用于清理無(wú)效的Entry
插入的時(shí)候可能會(huì)被調(diào)用
替換無(wú)效slot的時(shí)候可能會(huì)被調(diào)用
區(qū)別是前者傳入的n為元素個(gè)數(shù),后者為table的容量
private boolean cleanSomeSlots(int i, int n) { boolean removed = false; Entry[] tab = table; int len = tab.length; do { // i在任何情況下自己都不會(huì)是一個(gè)無(wú)效slot,所以從下一個(gè)開(kāi)始判斷 i = nextIndex(i, len); Entry e = tab[i]; if (e != null && e.get() == null) { n = len;// 擴(kuò)大掃描控制因子 removed = true; i = expungeStaleEntry(i); // 清理一個(gè)連續(xù)段 } } while ( (n >>>= 1) != 0); return removed; }expungeStaleEntry執(zhí)行一次全量清理
private int expungeStaleEntry(int staleSlot) { Entry[] tab = table; int len = tab.length; // expunge entry at staleSlot tab[staleSlot].value = null;//刪除value tab[staleSlot] = null;//刪除entry size--; //map的size遞減 // Rehash until we encounter null Entry e; int i; for (i = nextIndex(staleSlot, len);// 遍歷指定刪除節(jié)點(diǎn),所有后續(xù)節(jié)點(diǎn) (e = tab[i]) != null; i = nextIndex(i, len)) { ThreadLocal> k = e.get(); if (k == null) {//key為null,執(zhí)行刪除操作 e.value = null; tab[i] = null; size--; } else {//key不為null,重新計(jì)算下標(biāo) int h = k.threadLocalHashCode & (len - 1); if (h != i) {//如果不在同一個(gè)位置 tab[i] = null;//把老位置的entry置null(刪除) // 從h開(kāi)始往后遍歷,一直到找到空為止,插入 while (tab[h] != null) h = nextIndex(h, len); tab[h] = e; } } } return i; }get操作set的邏輯分析完成以后,get的源碼分析就很簡(jiǎn)單了
public T get() { Thread t = Thread.currentThread(); //從當(dāng)前線程中獲取ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) { //查詢當(dāng)前ThreadLocal變量實(shí)例對(duì)應(yīng)的Entry ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) {//獲取成功,直接返回 @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //如果map為null,即還沒(méi)有初始化,走初始化方法 return setInitialValue(); }setInitialValue根據(jù)initialValue()的value初始化ThreadLocalMap
private T setInitialValue() { T value = initialValue();//protected方法,用戶可以重寫(xiě) Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) //如果map不為null,把初始化value設(shè)置進(jìn)去 map.set(this, value); else //如果map為null,則new一個(gè)map,并把初始化value設(shè)置進(jìn)去 createMap(t, value); return value; }從當(dāng)前線程中獲取ThreadLocalMap,查詢當(dāng)前ThreadLocal變量實(shí)例對(duì)應(yīng)的Entry,如果不為null,獲取value,返回
如果map為null,即還沒(méi)有初始化,走初始化方法
remove方法remove的方法比較簡(jiǎn)單,從Entry[]中刪除指定的key就行
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();//調(diào)用Entry的clear方法 expungeStaleEntry(i);//清除陳舊數(shù)據(jù) return; } } }應(yīng)用場(chǎng)景ThreadLocal的實(shí)際應(yīng)用場(chǎng)景:
比如在線程級(jí)別,維護(hù)session,維護(hù)用戶登錄信息userID(登陸時(shí)插入,多個(gè)地方獲取)
數(shù)據(jù)庫(kù)的鏈接對(duì)象Connection,可以通過(guò)ThreadLocal來(lái)做隔離避免線程安全問(wèn)題
問(wèn)題ThreadLocal的內(nèi)存泄漏ThreadLocalMap中Entry的key使用的是ThreadLocal的弱引用,如果一個(gè)ThreadLocal沒(méi)有外部強(qiáng)引用,當(dāng)系統(tǒng)執(zhí)行GC時(shí),這個(gè)ThreadLocal勢(shì)必會(huì)被回收,這樣一來(lái),ThreadLocalMap中就會(huì)出現(xiàn)一個(gè)key為null的Entry,而這個(gè)key=null的Entry是無(wú)法訪問(wèn)的,當(dāng)這個(gè)線程一直沒(méi)有結(jié)束的話,那么就會(huì)存在一條強(qiáng)引用鏈
Thread Ref - > Thread -> ThreadLocalMap - > Entry -> value 永遠(yuǎn)無(wú)法回收而造成內(nèi)存泄漏
其實(shí)我們從源碼分析可以看到,ThreadLocalMap是做了防護(hù)措施的首先從ThreadLocal的直接索引位置(通過(guò)ThreadLocal.threadLocalHashCode & (len-1)運(yùn)算得到)獲取Entry e,如果e不為null并且key相同則返回e
如果e為null或者key不一致則向下一個(gè)位置查詢,如果下一個(gè)位置的key和當(dāng)前需要查詢的key相等,則返回對(duì)應(yīng)的Entry,否則,如果key值為null,則擦除該位置的Entry,否則繼續(xù)向下一個(gè)位置查詢
在這個(gè)過(guò)程中遇到的key為null的Entry都會(huì)被擦除,那么Entry內(nèi)的value也就沒(méi)有強(qiáng)引用鏈,自然會(huì)被回收。仔細(xì)研究代碼可以發(fā)現(xiàn),set操作也有類(lèi)似的思想,將key為null的這些Entry都刪除,防止內(nèi)存泄露。
但是這個(gè)設(shè)計(jì)一來(lái)與一個(gè)前提條件,就是調(diào)用get或者set方法,但是不是所有場(chǎng)景都會(huì)滿足這個(gè)場(chǎng)景的,所以為了避免這類(lèi)的問(wèn)題,我們可以在合適的位置手動(dòng)調(diào)用ThreadLocal的remove函數(shù)刪除不需要的ThreadLocal,防止出現(xiàn)內(nèi)存泄漏所以建議的使用方法是將ThreadLocal變量定義成private static的,這樣的話ThreadLocal的生命周期就更長(zhǎng),由于一直存在ThreadLocal的強(qiáng)引用,所以ThreadLocal也就不會(huì)被回收,也就能保證任何時(shí)候都能根據(jù)ThreadLocal的弱引用訪問(wèn)到Entry的value值,然后remove它,防止內(nèi)存泄露
每次使用完ThreadLocal,都調(diào)用它的remove()方法,清除數(shù)據(jù)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/72585.html
摘要:導(dǎo)讀閱讀本文需要有足夠的時(shí)間,筆者會(huì)由淺到深帶你一步一步了解一個(gè)資深架構(gòu)師所要掌握的各類(lèi)知識(shí)點(diǎn),你也可以按照文章中所列的知識(shí)體系對(duì)比自身,對(duì)自己進(jìn)行查漏補(bǔ)缺,覺(jué)得本文對(duì)你有幫助的話,可以點(diǎn)贊關(guān)注一下。目錄一基礎(chǔ)篇二進(jìn)階篇三高級(jí)篇四架構(gòu)篇五擴(kuò) 導(dǎo)讀:閱讀本文需要有足夠的時(shí)間,筆者會(huì)由淺到深帶你一步一步了解一個(gè)資深架構(gòu)師所要掌握的各類(lèi)知識(shí)點(diǎn),你也可以按照文章中所列的知識(shí)體系對(duì)比自身,對(duì)自己...
摘要:阿里巴巴的共享服務(wù)理念以及企業(yè)級(jí)互聯(lián)網(wǎng)架構(gòu)建設(shè)的思路,給這些企業(yè)帶來(lái)了不少新的思路,這也是我最終決定寫(xiě)這本書(shū)的最主要原因。盡在雙阿里巴巴技術(shù)演進(jìn)與超越是迄今唯一由阿里巴巴集團(tuán)官方出品全面闡述雙八年以來(lái)在技術(shù)和商業(yè)上演進(jìn)和創(chuàng)新歷程的書(shū)籍。 showImg(https://segmentfault.com/img/remote/1460000015386860); 1、大型網(wǎng)站技術(shù)架構(gòu):核...
摘要:但是還有另外的功能看的后一半代碼作用就是掃描位置之后的數(shù)組直到某一個(gè)為的位置,清除每個(gè)為的,所以使用可以降低內(nèi)存泄漏的概率。 在涉及到多線程需要共享變量的時(shí)候,一般有兩種方法:其一就是使用互斥鎖,使得在每個(gè)時(shí)刻只能有一個(gè)線程訪問(wèn)該變量,好處就是便于編碼(直接使用 synchronized 關(guān)鍵字進(jìn)行同步訪問(wèn)),缺點(diǎn)在于這增加了線程間的競(jìng)爭(zhēng),降低了效率;其二就是使用本文要講的 Threa...
摘要:前言最近開(kāi)發(fā)公司的項(xiàng)目,遇到了分布式的場(chǎng)景,即,同一條數(shù)據(jù)可能被多臺(tái)服務(wù)器或者說(shuō)多個(gè)線程同時(shí)修改,此時(shí)可能會(huì)出現(xiàn)分布式事務(wù)的問(wèn)題,隨即封裝了分布式鎖的注解。 前言 最近開(kāi)發(fā)公司的項(xiàng)目,遇到了分布式的場(chǎng)景,即,同一條數(shù)據(jù)可能被多臺(tái)服務(wù)器或者說(shuō)多個(gè)線程同時(shí)修改,此時(shí)可能會(huì)出現(xiàn)分布式事務(wù)的問(wèn)題,隨即封裝了redis分布式鎖的注解。 場(chǎng)景分析 前提:我的銀行卡有0元錢(qián),現(xiàn)在有A,B兩個(gè)人,想分...
閱讀 3132·2021-11-15 18:14
閱讀 1785·2021-09-22 10:51
閱讀 3300·2021-09-09 09:34
閱讀 3515·2021-09-06 15:02
閱讀 1032·2021-09-01 11:40
閱讀 3194·2019-08-30 13:58
閱讀 2535·2019-08-30 11:04
閱讀 1089·2019-08-28 18:31