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