摘要:通過將保存在中,每個線程都會擁有屬于自己的,代碼如下所示然后你就可以安心地調(diào)用了,不用考慮線程安全問題。這樣設(shè)計的好處就是,當(dāng)線程死掉之后,沒有強引用,方便收集器回收。
前言
想必大家都對Threadlocal很熟悉吧,今天我們就一起來深入學(xué)習(xí)一下。Threadlocal我更傾向于將其翻譯成線程局部變量。它有什么用處呢?Threadlocal對象通常用于防止對可變的單實例變量或全局變量進行共享。在spring中,通過將事務(wù)上下文保存在靜態(tài)的threadlocal中,當(dāng)框架代碼需要判斷當(dāng)前運行的是哪一個事務(wù)時,只需要從ThreadLocal對象中獲取事務(wù)上下文,這種機制很方便,避免了在每個方法都要傳遞上下文信息。
一個小例子眾所周知,SimpleDateFormat不是一個線程安全的類,在多線程環(huán)境下使用同一個實例是不正確的。通過將SimpleDateFormat保存在Threadlocal中,每個線程都會擁有屬于自己的SimpleDateFormat,代碼如下所示:
public class DateFormatUtil { public static ThreadLocaldataFormatlocal = new ThreadLocal () { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat(); } }; public static SimpleDateFormat getInstance() { return dataFormatlocal.get(); } }
然后你就可以安心地調(diào)用了,不用考慮線程安全問題。當(dāng)然你也可以在每個線程內(nèi)部調(diào)用new SimpleDateFormat(),但現(xiàn)實情況是大部分開發(fā)可能并不知道它是線程不安全的,并且將其封裝成一個單例的工具類。至于如何使用,全憑各位讀者愛好,這個例子主要用來演示Threadloca的基本用法。
腦洞大開---自己設(shè)計個threadlocal通過之前的演示,我們已經(jīng)大致知道threadlocal怎么用,并且清楚threadlocal保證了每個線程都有一個局部變量副本。如果以此為需求,讓我們自己去設(shè)計,會是什么樣子的呢?
static Mapthreadlocal = new HashMap ();
這就是我設(shè)計的....好吧(╯▽╰),原諒我水平有限....在這個hashMap中,threadId為key,Object為value,也是可以實現(xiàn)Threadlocal的。那么我們現(xiàn)在要來考慮幾個問題。jdk是這樣設(shè)計的嗎?這樣設(shè)計很low,但是low在哪了?
答:大師們當(dāng)然不是這樣設(shè)計了。如果這樣設(shè)計,每個線程的變量都會永久的保存在hashMap中,存在內(nèi)存泄漏。
low的寫法我們已經(jīng)見過了,現(xiàn)在我們一起來分下jdk是如何設(shè)計的,本文引用jdk1.8。
讓我們看下threadlocal的結(jié)構(gòu)圖:
類核心方法set、get、initialValue、setInitialValue、remove,后面主要圍繞著這幾個方法介紹。
類核心變量threadLocalHashCode,nextHashCode,HASH_INCREMENT,其中nextHashCode和HASH_INCREMENT都是靜態(tài)的,所以對于一個threadlocal對象,成員變量只有一個threadLocalHashCode,這是一個自定義的hash函數(shù),主要為了減少散列桶的沖突(不是本文重點,好奇的可以看下這篇博客https://www.cnblogs.com/ilell...)。
那到底Threadlocal將變量存到哪了呢?眼尖的同學(xué)肯定已經(jīng)發(fā)現(xiàn)了ThreadLocalMap,再來看下ThreadlocalMap的結(jié)構(gòu):
相信讀過hashMap源碼同學(xué)的一定會覺得非常眼熟,ThreadlocalMap的主體也是Entry[],有resize()和rehash(),區(qū)別僅僅就是在于沒有使用鏈表結(jié)構(gòu)和紅黑樹來處理散列沖突了。Entry的結(jié)構(gòu)我們也很有必要了解一下:
Entry繼承了一個弱引用,這里簡單介紹下弱引用的知識。弱引用是用來描述非必需對象的,被弱引用關(guān)聯(lián)的對象只能生存到下次垃圾收集發(fā)生之前。無論當(dāng)前內(nèi)存是否足夠,都會回收掉只被弱引用關(guān)聯(lián)的對象。很多面試官都喜歡問Threadlocal是怎么發(fā)生內(nèi)存泄漏的,其實就是在問這個,這個需要我們對Threadlocal有一個整體的了解才能明白,所以放在最后講。
當(dāng)我們看完Threadlocal的結(jié)構(gòu)了,我們發(fā)現(xiàn)ThreadlocalMap是用來存儲的數(shù)據(jù)結(jié)構(gòu),那它是怎么和線程關(guān)聯(lián)起來的呢?這里我們開始研究Threadlocal.set()方法.
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
步驟1:獲取當(dāng)前線程
步驟2:通過當(dāng)前線程獲取ThreadLocalMap
步驟3:如果map不為null,將當(dāng)前線程和value放到map中,否則創(chuàng)建一個map。
如何和線程綁定的玄機就在getMap(t)中。
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
public class Thread implements Runnable { /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; }
看到這里,我們就知道了原來Threadlocal有一個內(nèi)部類ThreadlocalMap,對于任何一個線程都會有唯一的一個ThreadlocalMap來對應(yīng),而這個map實際并不存儲在Threadlocal中,而是存在Thread當(dāng)中,只不過由Threadlocal暴露了一套api來維護Thread的ThreadLocalMap。這樣設(shè)計的好處就是,當(dāng)線程死掉之后,ThreadLocalMap沒有強引用,方便收集器回收。
繼續(xù)來看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(); }
如果前面的流程看懂了,這就很簡單了。當(dāng)ThreadLocalMap為null或者Entry為null的時候?qū){(diào)用setInitialValue();
private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
流程也很簡單,調(diào)用initialValue()初始化一個值,獲取當(dāng)前線程的ThreadLocalMap,后面的流程之前都已經(jīng)介紹過了,這里不再重復(fù),我們主要來看下initialValue()
protected T initialValue() { return null; }
請注意,protected修飾,return null;這是一個初始化值得方法,也就意味著如果業(yè)務(wù)允許的話,需要我們自己實現(xiàn)initialValue();
ThreadLocal的remove實現(xiàn)
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
ThreadLocalMap的remove實現(xiàn)
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; } } }
都非常簡單,就留給大家自己看了~。~.
Threadlocal容易發(fā)生內(nèi)存泄露?先回顧一下ThreadlocalMap的結(jié)構(gòu)
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{ /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } } ..... ..... }
然后套用網(wǎng)上的一張圖(實線表示強引用,虛線表示虛引用)
ThreadLocalMap使用ThreadLocal的弱引用作為key,如果一個ThreadLocal沒有外部強引用引用他,那么系統(tǒng)gc的時候,這個ThreadLocal勢必會被回收,這樣一來,ThreadLocalMap中就會出現(xiàn)key為null的Entry,就沒有辦法訪問這些key為null的Entry的value,如果當(dāng)前線程再遲遲不結(jié)束的話,這些key為null的Entry的value就會一直存在一條強引用鏈:ThreadLocal Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠無法回收,造成內(nèi)存泄露。其實jdk已經(jīng)考慮到了這種情況,ThreadLocalMap的genEntry函數(shù)或者set函數(shù)會去遍歷將key為null的給移除掉,但這明顯不是所有情況都成立的,所以需要調(diào)用者自己去調(diào)用remove函數(shù),手動刪除掉需要的threadlocal,防止內(nèi)存泄露。然后jdk并不建議在棧內(nèi)聲明threadlocal,而是建議將ThreadLocal變量定義成private static的,這樣的話ThreadLocal的生命周期就更長,由于一直存在ThreadLocal的強引用,所以ThreadLocal也就不會被回收,也就能保證任何時候都能根據(jù)ThreadLocal的弱引用訪問到Entry的value值,然后remove它,防止內(nèi)存泄露。
放圖證明這是jdk說的~~
再不睡覺,就天明了..
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://www.ezyhdfw.cn/yun/69665.html
摘要:具體怎么實現(xiàn)的呢,思想其實特別簡單,我們在深入理解中的變量上一文的最后有提起過,就是創(chuàng)建一個全局字典,然后將線程或者協(xié)程標識符作為,相應(yīng)線程或協(xié)程的局部數(shù)據(jù)作為。 在上篇我們看到了 ThreadLocal 變量的簡單使用,中篇對python中 ThreadLocal 的實現(xiàn)進行了分析,但故事還沒有結(jié)束。本篇我們一起來看下Werkzeug中ThreadLocal的設(shè)計。 Werkzeug...
摘要:在深入理解中的變量上中我們看到的引入,使得可以很方便地在多線程環(huán)境中使用局部變量。特別需要注意的是,基類的并不會屏蔽派生類中的創(chuàng)建。到此,整個源碼核心部分已經(jīng)理解的差不多了,只剩下用來執(zhí)行清除工作。 在 深入理解Python中的ThreadLocal變量(上) 中我們看到 ThreadLocal 的引入,使得可以很方便地在多線程環(huán)境中使用局部變量。如此美妙的功能到底是怎樣實現(xiàn)的?如果你...
摘要:雖然類名中帶有字樣,但是實際上并不是接口的子類。是弱連接接口,這意味著如果僅有指向某一類,其任然有可能被回收掉。這里使用弱連接的意義,是為了防止業(yè)務(wù)代碼中置空對象,但是由于存在連接可達,所以仍然無法回收掉該對象的情況發(fā)生。 零 前期準備 0 FBI WARNING 文章異常啰嗦且繞彎。 1 版本 JDK 版本 : OpenJDK 11.0.1 IDE : idea 2018.3 2 T...
摘要:方法,刪除當(dāng)前線程綁定的這個副本數(shù)字,這個值是的值,普通的是使用鏈表來處理沖突的,但是是使用線性探測法來處理沖突的,就是每次增加的步長,根據(jù)參考資料所說,選擇這個數(shù)字是為了讓沖突概率最小。 showImg(https://segmentfault.com/img/remote/1460000019828633); 老套路,先列舉下關(guān)于ThreadLocal常見的疑問,希望可以通過這篇學(xué)...
閱讀 633·2023-04-25 21:29
閱讀 1194·2023-04-25 21:27
閱讀 1110·2021-11-25 09:43
閱讀 1173·2021-09-29 09:43
閱讀 3685·2021-09-03 10:30
閱讀 2923·2019-08-29 15:26
閱讀 2882·2019-08-29 12:52
閱讀 1809·2019-08-29 11:10