ThreadLocal 原理

簡介

ThreadLoal 變量定躏,線程局部變量账磺,同一個 ThreadLocal 所包含的對象,在不同的 Thread 中有不同的副本痊远。這里有幾點需要注意:

  • 因為每個 Thread 內(nèi)有自己的實例副本垮抗,且該副本只能由當(dāng)前 Thread 使用。這是也是 ThreadLocal 命名的由來碧聪。
  • 既然每個 Thread 有自己的實例副本冒版,且其它 Thread 不可訪問,那就不存在多線程間共享的問題矾削。
  • ThreadLocal 提供了線程本地的實例壤玫。它與普通變量的區(qū)別在于,每個使用該變量的線程都會初始化一個完全獨立的實例副本哼凯。ThreadLocal 變量通常被private static修飾欲间。當(dāng)一個線程結(jié)束時,它所使用的所有 ThreadLocal 相對的實例副本都可被回收断部。

總的來說猎贴,ThreadLocal 適用于每個線程需要自己獨立的實例且該實例需要在多個方法中被使用,也即變量在線程間隔離而在方法或類間共享的場景。

使用

private static final ThreadLocal<T> threadLocal = new ThreadLocal<T>();
threadLocal.set();
threadLocal.get();
threadLocal.remove();

原理

類圖

ThreadLocal.png
//ThreadLocal是個泛型類她渴,保證可以接受任何類型的對象
public class ThreadLocal<T> {
    ...
    static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
       ...
    }
    //ThreadLocalMap 用來存放數(shù)據(jù)
    static class ThreadLocalMap {
         static class Entry extends WeakReference<ThreadLocal<?>> {
              ...
          }
    }
    ...
}

通過代碼理解原理

ThreadLocal提供了線程內(nèi)存儲變量的能力达址,這些變量在每個線程之間是相互獨立的。我們可以通過get和set方法得到或設(shè)置當(dāng)前線程對應(yīng)的值趁耗。

設(shè)置數(shù)據(jù) set()
public void set(T value) {
    //獲取當(dāng)前線程
    Thread t = Thread.currentThread();
    //獲取數(shù)據(jù)實際的存儲結(jié)構(gòu)
    ThreadLocalMap map = getMap(t);
    //如果map為空
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    //t為當(dāng)前線程沉唠,在當(dāng)前線程中維護了一個ThreadLocalMap
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    //將新創(chuàng)建的ThreadLocalMap賦值給Thread中的threadLocals變量
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
獲取數(shù)據(jù) get()
public T get() {
     //獲取當(dāng)前線程
    Thread t = Thread.currentThread();
    //獲取數(shù)據(jù)實際的存儲結(jié)構(gòu)
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //以當(dāng)前ThreadLocal對象為key獲取對應(yīng)存儲的value
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

private T setInitialValue() {
    //如果還沒有初始化ThreadLocalMap,則會創(chuàng)建并將初始化的value值設(shè)置到ThreadLocalMap中
    T value = initialValue();//初始化的value為 null
    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;
}
remove()
 public void remove() {
     ThreadLocalMap m = getMap(Thread.currentThread());
     if (m != null)
         m.remove(this);
 }

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

ThreadLocalMap是ThreadLocal的靜態(tài)內(nèi)部類苛败,其存儲結(jié)構(gòu)使用的是Entry满葛,實際為一個WeakReference弱引用,使用弱引用的原因為當(dāng)ThreadLocal沒有強引用的情況下罢屈,在垃圾回收的時候key可以及時的被清理掉嘀韧。但是請注意value還是強引用,不會被清理掉缠捌,所以會出現(xiàn)key為null的value锄贷,所以在不在使用的時候及時調(diào)用remove方法清除。

static class ThreadLocalMap {
    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<?> 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);
    }
    ...
}

內(nèi)存泄露問題

通過上面的一些介紹可以知道曼月,ThreadLocalMap中維護了一個Entry的數(shù)據(jù)結(jié)構(gòu)谊却,其中key使用了弱引用來保證GC時回收,但是value部分仍然存在強引用無法回收導(dǎo)致泄露十嘿。


內(nèi)存泄露

使用場景

1因惭、在進行對象跨層傳遞的時候,使用ThreadLocal可以避免多次傳遞绩衷,打破層次間的約束。
2激率、線程間數(shù)據(jù)隔離
3咳燕、進行事務(wù)操作,用于存儲線程事務(wù)信息乒躺。(Spring聲明式事務(wù))
4招盲、數(shù)據(jù)庫連接,Session會話管理嘉冒。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末曹货,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子讳推,更是在濱河造成了極大的恐慌顶籽,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件银觅,死亡現(xiàn)場離奇詭異礼饱,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門镊绪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匀伏,“玉大人,你說我怎么就攤上這事蝴韭」坏撸” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵榄鉴,是天一觀的道長摧找。 經(jīng)常有香客問我,道長牢硅,這世上最難降的妖魔是什么蹬耘? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮减余,結(jié)果婚禮上综苔,老公的妹妹穿的比我還像新娘。我一直安慰自己位岔,他們只是感情好如筛,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著抒抬,像睡著了一般杨刨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上擦剑,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天妖胀,我揣著相機與錄音,去河邊找鬼惠勒。 笑死赚抡,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的纠屋。 我是一名探鬼主播涂臣,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼售担!你這毒婦竟也來了赁遗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤族铆,失蹤者是張志新(化名)和其女友劉穎岩四,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體骑素,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡炫乓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年刚夺,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片末捣。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡侠姑,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出箩做,到底是詐尸還是另有隱情莽红,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布邦邦,位于F島的核電站安吁,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏燃辖。R本人自食惡果不足惜鬼店,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望黔龟。 院中可真熱鬧妇智,春花似錦、人聲如沸氏身。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蛋欣。三九已至航徙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間陷虎,已是汗流浹背到踏。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留泻红,地道東北人夭禽。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像谊路,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子菩彬,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容