共享變量一直是併發中的老大難問題,每一個線程都對它有操做權,因此線程之間的同步很關鍵,鎖也就應運而生。這裏換一個思路,是否能夠把共享變量私有化?即每一個線程都擁有一份共享變量的本地副本,每一個線程對應一個副本,同時對共享變量的操做也改成對屬於本身的副本的操做,這樣每一個線程處理本身的本地變量,造成數據隔離。事實上這就是ThreadLocal了。併發
就線程同步而言,鎖能夠認爲是時間換空間,ThreadLocal能夠認爲是空間換時間,其實我的以爲這麼描述有點強行往同步靠的意思,專業的人幹專業的事,同步這個仍是就老老實實交給鎖吧。 ThreadLocal最適合的是變量在線程間隔離而在方法或類間共享的場景。app
咱們可能天天都在使用Spring寫dao寫service,真的是一個很是爽的框架.咱們能用這麼爽是由於Spring在底層把髒活兒累活兒全乾了。在一個Service中咱們可能要寫不少個dao,若是多個dao都用不一樣的JDBC鏈接,很費時費力費資源不說,事務性就得不到保證了,由於咱們知道事務須要在一個鏈接內才能得以實現。事務對應着鏈接,因此若是咱們每一個線程對應一個鏈接,也就能保證咱們在一個service中很爽的叨叨叨了,Spring底層正是使用ThreadLocal對鏈接進行了封裝,可勁兒叨吧你就框架
slf4j中有一個類叫MDC,全名叫Mapped Diagnositc Context,這個類基於 InheritableThreadLocal ,先不細說,暫且把它當成ThreadLocal。咱們能夠給每一個服務或用戶行爲編號,每次客戶調用服務就把編號寫到threadlocal包含的變量中,最後打印出來。彷佛有點dubbo鏈路監控的意思(然而我dubbo才學會拼寫沒多久,沒看過源碼就不妄議了hhh)異步
看完應用場景彷佛對Threadlocal已經有所瞭解,不過,本着知其然並知其因此然的心態,仍是去學習下源碼怎麼實現的吧源碼分析
先去Thread類中瞄一眼,Thread中有2個成員變量學習
ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
兩個變量都是ThreadLocalMap類,咦?說了半天 ThreadLocal怎麼又冒出來個ThreadLocalMapthis
別急,是這樣子的,默認這兩個變量都是null,正如咱們如今看到的。spa
一旦咱們調用ThreadLocal的set或者get纔會真正建立他們。也就是你覺得你把變量交給ThreadLocal了,其實這小子轉手就給ThreadLocalMap了,ThreadLocal就是套在 ThreadLocalMap外面的一層殼而已。線程
ThreadLocal的組成以下3d
能夠看出,就跟map基本同樣,key是ThreadLocal的引用,value則是由開發者設置,即本地變量
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) // 這裏this是 ThreadLocal的實例引用 map.set(this, value); else createMap(t, value); } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
再次注意值存放的實際位置是Thread中的ThreadLocalMap變量,ThreadLocalMap是一個map,key是ThreadLocal的實例引用,值則是咱們要存的變量
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(); }
其實就很簡單了,set的反過程嘛,先獲得當前線程,而後獲得成員變量threadLocals,若是threadLocals不爲空,返回本地變量對應值,不然初始化threadLocals
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; } protected T initialValue() { return null; }
判斷當前threadLocals是否爲空,若是不爲空,設置當前ThreadLocal的實例引用對應變量爲null,不然調用createMap建立threadLocals變量
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
若是threadLocals變量不爲空,刪掉map中當前ThreadLocal對應實例引用的本地變量。
從上邊一路走下來咱們應該瞭解了,每個線程中都有一個ThreadLocalMap
類型的threadLocals變量,這個map中key爲ThreadLocal的實例引用,value爲對應的本地變量。若是這個線程不消亡,開發者也沒有采用remove操做及時清除掉再也不使用的變量,這些變量就會一直存在map中,直到撐爆你的內存,形成內存溢出問題
假設一個場景,咱們須要異步起一個線程發送郵件給用戶,子線程須要打日誌,那麼就須要父線程中本地變量,這裏子線程能夠經過父線程的ThreadLocal獲得父線程設置的的本地變量嗎?答案是不能夠,ThreadLocal不支持父子線程間的繼承傳遞
這裏可使用InheritableThreadLocal,沒錯就是上邊日誌那塊咱們提到過的。它是ThreadLocal的子類,其實用法跟ThreadLocal徹底同樣,不過,它可使得子線程訪問在父線程中設置的變量,簡單看下這一實現的原理
首先看下線程的構造方法,說實話以前真沒看過這個構造方法,我以爲應該不少人都沒看過吧
public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); }
這個init方法追進去,你會看到底下這行代碼,init方法體太長,我就只截取出來這最關鍵的一部分
if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
parent就是父線程,若是父線程中的inheritableThreadLocals 不爲空,就執行 createInheritedMap
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { return new ThreadLocalMap(parentMap); }
這個代碼就不繼續展開了,過程就是把父線程中的 inheritableThreadLocals 的值所有複製一份到子線程的 inheritableThreadLocals 中
以爲還能夠的話,關注一哈我呀哈哈哈