ThreadLocal原理解析以及是否需要调用remove方法
平常的開(kāi)發(fā)過(guò)程中,如果有個(gè)類不是線程安全的,比如SimpleDateFormat,要使這個(gè)類在并發(fā)的過(guò)程中是線程安全的,那么可以將變量設(shè)置位局部變量,不過(guò)存在的問(wèn)題就是頻繁的創(chuàng)建對(duì)象,對(duì)性能和資源會(huì)有一定降低和消耗;那么這里就可以用到ThreadLocal作為線程隔離,那么ThreadLocal是如何實(shí)現(xiàn)線程與線程之間隔離的呢,待會(huì)兒會(huì)在下文進(jìn)行講解。
之前有篇使用ThreadLocal做線程之間的隔離實(shí)例,大家可以參考一下:使用ThreadLocal實(shí)現(xiàn)Mybatis多數(shù)據(jù)源
JDK引用類型
在了解ThreadLocal原理之前,先讓大家了解一下JDK中的引用類型。
JDK共有四種引用類型:
1、強(qiáng)引用類型:就是平常創(chuàng)建的對(duì)象都屬于強(qiáng)引用類型,比如 Object object = new Object();該object為強(qiáng)引用類型,如果該引用沒(méi)有主動(dòng)置為null,那么該引用的對(duì)象就不會(huì)被GC回收,所以一般在寫(xiě)完一段業(yè)務(wù)之后都會(huì)將用到的對(duì)象引用置為null,就是為了輔助GC更好的進(jìn)行垃圾回收。
2、軟引用類型:比強(qiáng)引用的類型弱一點(diǎn),在應(yīng)用程序發(fā)生OOM(內(nèi)存溢出)之前就會(huì)去回收這些弱引用占用的內(nèi)存,使用的SoftReference類,使用示例如下:
?3、弱引用類型:比軟引用類型還要弱一點(diǎn),在下一次發(fā)生GC回收之前就會(huì)被垃圾回收器進(jìn)行回收,使用WeakReference類,使用示例如下:
4、虛引用類型:這個(gè)是JDK中最弱的引用類型,在對(duì)象被回收之前會(huì)移入到一個(gè)隊(duì)列當(dāng)中,然后在進(jìn)行刪除,這個(gè)引用類型用的不多,在JDK中引用的類是PhantomReference,這個(gè)需結(jié)合引用隊(duì)列(ReferenceQueue)以及重寫(xiě)finalize方法進(jìn)行使用,使用示例如下:
?
?對(duì)于應(yīng)用場(chǎng)景來(lái)說(shuō),如果當(dāng)前對(duì)象為可有可無(wú)的話,那么可以使用軟引用或者弱引用進(jìn)行使用,而對(duì)應(yīng)虛引用的話,個(gè)人認(rèn)為可以適用在非GC回收的區(qū)域(比如:元空間MetaSpace)使用,可以用來(lái)監(jiān)測(cè)這些區(qū)域的回收情況等。
基本原理
ThreadLocal的用法呢,這里就先不談;底層使用的是ThreadLocalMap這個(gè)Map的底層采用的是ThreadLocalMap.Entry,ThreadLocalMap這個(gè)類在每個(gè)線程當(dāng)中都會(huì)存在一份對(duì)應(yīng)的單獨(dú)得對(duì)象,在Thread類中變量如下:
? ? /* ThreadLocal values pertaining to this thread. This map is maintained
? ? ?* by the ThreadLocal class. */
? ? ThreadLocal.ThreadLocalMap threadLocals = null;
這里就是線程安全的重點(diǎn),因?yàn)椴l(fā)的情況下,每個(gè)線程都對(duì)應(yīng)著有份自己的ThreadLocalMap,所以就不存在多線程競(jìng)爭(zhēng)資源問(wèn)題,所以如果使用ThreadLocal建議和線程一起使用,以為這樣可以減少系統(tǒng)的性能開(kāi)銷以及對(duì)ThreadLocal對(duì)象的一種復(fù)用,提升系統(tǒng)性能。
看一下ThreadLocalMap,會(huì)發(fā)現(xiàn)Entry繼承了WeakReference類,說(shuō)明這個(gè)類創(chuàng)建出來(lái)的對(duì)象是弱引用對(duì)象
static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;} } //容量 private static final int INITIAL_CAPACITY = 16; //存儲(chǔ)屬性值 private Entry[] table; //構(gòu)造初始化table數(shù)組 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); }我們開(kāi)看一下get方法:獲取當(dāng)前線程,然后通過(guò)調(diào)用getMap方法獲取到當(dāng)前線程的ThreadLocalMap對(duì)象,對(duì)于剛開(kāi)始初始化的線程或者在ThreadLocalMap中沒(méi)有找到來(lái)說(shuō),那么就會(huì)走下面的setInitialValue方法,如果已經(jīng)初始化的ThreadLocalMap來(lái)說(shuō),會(huì)直接獲取對(duì)應(yīng)的Entry對(duì)象
/*** Returns the value in the current thread's copy of this* thread-local variable. If the variable has no value for the* current thread, it is first initialized to the value returned* by an invocation of the {@link #initialValue} method.** @return the current thread's value of this thread-local*/public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}在setInitialValue方法中,會(huì)發(fā)現(xiàn)去獲取初始化方法中的對(duì)象,如果在創(chuàng)建ThreadLocal沒(méi)有重寫(xiě)初始化方法的話,那么就會(huì)返回null,或者在使用前調(diào)用set方法重新設(shè)置一下當(dāng)前線程中的ThreadLocalMap中的屬性初始化值,大家可以各自去看一下set方法;
private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value; }這里列一下getEntry方法,根據(jù)ThreadLocal對(duì)象計(jì)算出hash值找到對(duì)應(yīng)的table數(shù)組的位置,并且獲取到這個(gè)對(duì)象。
private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.get() == key)return e;elsereturn getEntryAfterMiss(key, i, e); }問(wèn)題
以為這就完了?并沒(méi)有,規(guī)范的使用為在使用過(guò)get方法應(yīng)該在調(diào)用remove方法進(jìn)行刪除(即將當(dāng)前線程ThreadLocalMap中的引用置為空并且table數(shù)組中的Entry值也置為空);如果是線程池的話,那么這些數(shù)據(jù)就會(huì)一直存在,如果沒(méi)有及時(shí)刪除會(huì)造成內(nèi)存泄漏。
調(diào)用remove方法回去執(zhí)行ThreadLocalMap的remove方法,而在這個(gè)方法中,通過(guò)計(jì)算得到對(duì)應(yīng)的Entry數(shù)組的位置,并且進(jìn)行引用清除以及table數(shù)組清空,避免內(nèi)存泄漏問(wèn)題,
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this); } /*** Remove the entry for key.*/ 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) {//將Entry本身的引用ThreadLocal置為空e.clear();//清空table數(shù)組中Entry.valueexpungeStaleEntry(i);return;}} }但是如果是經(jīng)常性用到的ThreadLocal的話,個(gè)人建議可以不用刪除,因?yàn)槿绻l繁使用的話,置為null,后面又會(huì)重新調(diào)用在ThreadLocalMap未找到,那么就會(huì)調(diào)用setInitialValue方法,重新創(chuàng)建對(duì)象并且賦值,在某種意義上可以說(shuō)是和局部變量是一樣的了,這樣就違背了當(dāng)初減少性能開(kāi)銷的需求了。
在加上Entry繼承了WeakReference類,所以創(chuàng)建的對(duì)象會(huì)是個(gè)弱引用類型,在GC進(jìn)行回收時(shí)候會(huì)被回收掉的,如果回收掉了引用對(duì)象,那么Entry中的value變量值是否還存在呢;
繼續(xù)解析
注意這里是回收掉引用的對(duì)應(yīng),即ref.get()為null值,但是弱引用本身這個(gè)對(duì)象是還在的,我們看一下在setInitialValue方法中是如何處理的,重新設(shè)置里面,如果獲取到ThreadLocal引用沒(méi)有獲取到,說(shuō)明這個(gè)弱引用被回收了,這里就會(huì)去調(diào)用replaceStaleEntry方法。
private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);//重新設(shè)置值elsecreateMap(t, value);return value; } //ThreadLocalMapprivate 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) {e.value = value;return;}if (k == null) {replaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash(); }?而在replaceStaleEntry方法中有個(gè)這么一行代碼,將value變量置為null并且重新創(chuàng)建Entry對(duì)象,所以就算是沒(méi)有調(diào)用remove刪除方法,在GC過(guò)后依舊會(huì)置為null。
? ? ? ? ? ? // If key not found, put new entry in stale slot
? ? ? ? ? ? tab[staleSlot].value = null;
? ? ? ? ? ? tab[staleSlot] = new Entry(key, value);
在此會(huì)有個(gè)小問(wèn)題,為啥不用SoftReference而是使用WeakReference,個(gè)人覺(jué)得如果使用軟引用的話,如果是使用線程池并且ThreadLocal會(huì)頻繁訪問(wèn)的話,那么是可以的,但是實(shí)際應(yīng)用并非只有這種情況,而且在發(fā)生OOM之前,只會(huì)回收掉軟引用對(duì)象,但是Entry中的value變量還在,并不能真正的回收掉值,只有等到下一次使用的時(shí)候才能置為null,所以綜合來(lái)看使用WeakReference還是最好的選擇。
歡迎各位大佬一起討論
總結(jié)
以上是生活随笔為你收集整理的ThreadLocal原理解析以及是否需要调用remove方法的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: MongoDB数据库的迁移
- 下一篇: 咖啡豆的励志故事