想必你們都對Threadlocal很熟悉吧,今天咱們就一塊兒來深刻學習一下。Threadlocal我更傾向於將其翻譯成線程局部變量。它有什麼用處呢?Threadlocal對象一般用於防止對可變的單實例變量或全局變量進行共享。在spring中,經過將事務上下文保存在靜態的threadlocal中,當框架代碼須要判斷當前運行的是哪個事務時,只須要從ThreadLocal對象中獲取事務上下文,這種機制很方便,避免了在每一個方法都要傳遞上下文信息。html
衆所周知,SimpleDateFormat不是一個線程安全的類,在多線程環境下使用同一個實例是不正確的。經過將SimpleDateFormat保存在Threadlocal中,每一個線程都會擁有屬於本身的SimpleDateFormat,代碼以下所示:面試
public class DateFormatUtil { public static ThreadLocal<SimpleDateFormat> dataFormatlocal = new ThreadLocal<SimpleDateFormat>() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat(); } }; public static SimpleDateFormat getInstance() { return dataFormatlocal.get(); } }
而後你就能夠安心地調用了,不用考慮線程安全問題。固然你也能夠在每一個線程內部調用new SimpleDateFormat(),但現實狀況是大部分開發可能並不知道它是線程不安全的,而且將其封裝成一個單例的工具類。至於如何使用,全憑各位讀者愛好,這個例子主要用來演示Threadloca的基本用法。spring
經過以前的演示,咱們已經大體知道threadlocal怎麼用,而且清楚threadlocal保證了每一個線程都有一個局部變量副本。若是以此爲需求,讓咱們本身去設計,會是什麼樣子的呢?api
static Map<String,Object> threadlocal = new HashMap<String,Object>();
這就是我設計的....好吧(╯▽╰),原諒我水平有限....在這個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的結構:ide
相信讀過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; } } }
都很是簡單,就留給你們本身看了~。~.
先回顧一下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<ThreadLocal> { /** 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說的~~
再不睡覺,就天明瞭..