一文帶你完全搞懂ThreadLocal

前言

共享變量一直是併發中的老大難問題,每一個線程都對它有操做權,因此線程之間的同步很關鍵,鎖也就應運而生。這裏換一個思路,是否能夠把共享變量私有化?即每一個線程都擁有一份共享變量的本地副本,每一個線程對應一個副本,同時對共享變量的操做也改成對屬於本身的副本的操做,這樣每一個線程處理本身的本地變量,造成數據隔離。事實上這就是ThreadLocal了。併發

就線程同步而言,鎖能夠認爲是時間換空間,ThreadLocal能夠認爲是空間換時間,其實我的以爲這麼描述有點強行往同步靠的意思,專業的人幹專業的事,同步這個仍是就老老實實交給鎖吧。 ThreadLocal最適合的是變量在線程間隔離而在方法或類間共享的場景。app

ThreadLocal的應用場景

ThreadLocal在Spring中事務的應用

咱們可能天天都在使用Spring寫dao寫service,真的是一個很是爽的框架.咱們能用這麼爽是由於Spring在底層把髒活兒累活兒全乾了。在一個Service中咱們可能要寫不少個dao,若是多個dao都用不一樣的JDBC鏈接,很費時費力費資源不說,事務性就得不到保證了,由於咱們知道事務須要在一個鏈接內才能得以實現。事務對應着鏈接,因此若是咱們每一個線程對應一個鏈接,也就能保證咱們在一個service中很爽的叨叨叨了,Spring底層正是使用ThreadLocal對鏈接進行了封裝,可勁兒叨吧你就框架

ThreadLocal在日誌中的應用

slf4j中有一個類叫MDC,全名叫Mapped Diagnositc Context,這個類基於 InheritableThreadLocal ,先不細說,暫且把它當成ThreadLocal。咱們能夠給每一個服務或用戶行爲編號,每次客戶調用服務就把編號寫到threadlocal包含的變量中,最後打印出來。彷佛有點dubbo鏈路監控的意思(然而我dubbo才學會拼寫沒多久,沒看過源碼就不妄議了hhh)異步

看完應用場景彷佛對Threadlocal已經有所瞭解,不過,本着知其然並知其因此然的心態,仍是去學習下源碼怎麼實現的吧源碼分析

源碼分析

ThreadLocalMap

先去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則是由開發者設置,即本地變量

ThreadLocal

set

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);
}
  1. 首先獲取當前線程
  2. 以當前線程爲key去查找當前線程的map即threadLocals變量
  3. 若是threadLocals不爲空,就把ThreadLocal引用做爲key,value傳給map
  4. 不然建立map,也就是初始化當前線程的threadLocals變量
再次注意值存放的實際位置是Thread中的ThreadLocalMap變量,ThreadLocalMap是一個map,key是ThreadLocal的實例引用,值則是咱們要存的變量

get

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

初始化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變量

remove()

public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
}

若是threadLocals變量不爲空,刪掉map中當前ThreadLocal對應實例引用的本地變量。

ThreadLocal的內存溢出問題

從上邊一路走下來咱們應該瞭解了,每個線程中都有一個ThreadLocalMap
類型的threadLocals變量,這個map中key爲ThreadLocal的實例引用,value爲對應的本地變量。若是這個線程不消亡,開發者也沒有采用remove操做及時清除掉再也不使用的變量,這些變量就會一直存在map中,直到撐爆你的內存,形成內存溢出問題

ThreadLocal的繼承問題

假設一個場景,咱們須要異步起一個線程發送郵件給用戶,子線程須要打日誌,那麼就須要父線程中本地變量,這裏子線程能夠經過父線程的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 中

總結

  1. 每一個線程對應一個ThreadLocalMap,ThreadLocal其實就是套在ThreadLocalMap上的一層殼
  2. ThreadLocalMap的key是ThreadLocal的實例引用,value是咱們像設置的本地變量
  3. 如果線程一直不停,threadLocalMap中的本地變量就會愈來愈多,注意即便remove掉再也不使用的變量,防止內存溢出

以爲還能夠的話,關注一哈我呀哈哈哈
wx_img.png

相關文章
相關標籤/搜索