Java源碼—ThreadLocal

前言

都知道ThreadLocal能夠保存變量的副本膀曾,使得各個(gè)線程的訪問不受影響,今天看看是如何做到這一點(diǎn)的。

使用

先來看一個(gè)ThreadLocal的簡單用法

// 定義一個(gè)ThreadLocal變量,存儲類型是Integer
private static final ThreadLocal<Integer> mThreadLocal = new ThreadLocal<>();

public static void main(String[] args) {
            // 主線程set
        mThreadLocal.set(1);
        System.out.println(Thread.currentThread().getName() + "——>" + mThreadLocal.get());

        new Thread("Thread#1") {
            @Override
            public void run() {
                super.run();
              // 先讀取
               System.out.println(this.getName() + "——>" + mThreadLocal.get());
              // 子線程set
                mThreadLocal.set(2);
                System.out.println(this.getName() + "——>" + mThreadLocal.get());
            }
        }.start();

        new Thread("Thread#2") {
            @Override
            public void run() {
                super.run();
              // 子線程get
                System.out.println(this.getName() + "——>" + mThreadLocal.get());
            }
        }.start();
    }

上面的例子很簡單,聲明了ThreadLocal變量紧唱,存儲類型是Integer, 在main函數(shù)中,先set(1)隶校,并讀取打印漏益。隨后啟動兩個(gè)子線程,分別操作這個(gè)ThreadLocal變量深胳。我們來看一下這個(gè)程序的運(yùn)行結(jié)果绰疤。

main——>1
  
Thread#1——>null
Thread#1——>2
  
Thread#2——>null

從運(yùn)行結(jié)果來看,各個(gè)線程對ThreadLcoal變量的操作舞终,只在其線程內(nèi)部生效轻庆,并不會影響其它線程癣猾,接下來我們看看這點(diǎn)是如何做到的。

源碼分析

先看看類的定義

/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
*/
public class ThreadLocal<T> {}

從注釋就可以看出余爆,關(guān)鍵的方法是setget纷宇,拿我們就直奔主題。

set方法

public void set(T value) {
    Thread t = Thread.currentThread(); // 首先拿到當(dāng)前的線程對象
    ThreadLocalMap map = getMap(t); // 拿到當(dāng)前線程對象中的一個(gè)ThreadLocalMap實(shí)例
    if (map != null) { // 操作map對象
        map.set(this, value);
    } else {
        createMap(t, value);
    }
}

這個(gè)方法最終是將value值存儲到一個(gè)ThreadLocalMap對象中蛾方,key是當(dāng)前當(dāng)前ThreadLocal對象像捶,value為實(shí)際值。

接下來看看ThreadLocalMap map = getMap(t);這句話

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

返回的是Thread對象中的變量

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

這里注意ThreadLocalMap是定義在ThreadLocal中的一個(gè)靜態(tài)類桩砰,他并不是我們廣義理解的Map拓春,看看他的定義。

static class ThreadLocalMap {
        // 內(nèi)部繼續(xù)定一個(gè)Entry, 封裝key value
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    private static final int INITIAL_CAPACITY = 16;
    private Entry[] table; // 一個(gè)Entry數(shù)組
    private int size = 0;
    ……
 }

ThreadLocalMap內(nèi)部又定一個(gè)Entry類亚隅,并聲明一個(gè)Entry數(shù)組硼莽;因此我們應(yīng)該清楚了此map非彼map
那么繼續(xù)看看這個(gè)所謂的map是如何做set處理的煮纵。

 private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1); // 拿到這個(gè)ThreadLocal的一個(gè)hash值
    
    // 處理hash沖突懂鸵,這里使用簡單的線性hash解決hash沖突
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        
        // 說明是重置,直接修改當(dāng)前entry的value即可
        if (k == key) {
            e.value = value;
            return;
        }
        
        // 說明已經(jīng)被回收了行疏,需要啟動清理矾瑰,key是弱引用
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
        
        // 在當(dāng)前位置插入元素
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold) // 擴(kuò)容處理
        rehash();
}

到這里我們應(yīng)該清楚了set方法干得事,也應(yīng)該明白了隘擎,為什么ThreadLocal變量做到了線程數(shù)據(jù)備份,并且線程之間不會相互影響凉夯。

  • 因?yàn)槊看握{(diào)用set货葬,都是操作的當(dāng)前線程中的ThreadLocalMap對象。

get方法

其實(shí)了解了set的實(shí)現(xiàn)機(jī)制劲够,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();
}

get方法同樣是拿到當(dāng)前線程的ThreadLocalMap對象,在該對象上去查找當(dāng)前ThreadLocal變量的存儲value征绎。

這里我們重點(diǎn)分析一下ThreadLocalMap.Entry e = map.getEntry(this);

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1); // 計(jì)算下標(biāo)
    Entry e = table[i]; // 從table數(shù)組中獲取Entry
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

結(jié)論

  1. ThreadLocal變量在賦值(set)和讀榷捉恪(get)的時(shí)候都先拿到了當(dāng)前線程中的ThreadLocalMap對象,該對象是當(dāng)前的線程的成員變量人柿;這回答了ThreadLocal能夠?qū)崿F(xiàn)線程數(shù)據(jù)備份的原因柴墩。

  2. ThreadLocalMap并不是平時(shí)所說的Map結(jié)構(gòu),他是ThreadLocal的靜態(tài)內(nèi)部類凫岖;并且在其內(nèi)部還定義了Entry數(shù)據(jù)結(jié)構(gòu)和一個(gè)Entry數(shù)組江咳。set和get都作用在這個(gè)數(shù)組之上。

  3. 今天簡單的看了set和get方法哥放,以及其中牽扯到的一些東西歼指。其中還有許多其它值得探究的問題爹土,比如內(nèi)存泄漏,map中的value是強(qiáng)引用踩身。還有hash計(jì)算等胀茵。ThreadLocal中的內(nèi)存泄露推薦面試必備:ThreadLocal詳解

水平有限,若有不當(dāng)挟阻,請指出G砟铩!赁濒!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末轨奄,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子拒炎,更是在濱河造成了極大的恐慌挪拟,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件击你,死亡現(xiàn)場離奇詭異玉组,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)丁侄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門惯雳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鸿摇,你說我怎么就攤上這事石景。” “怎么了拙吉?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵潮孽,是天一觀的道長。 經(jīng)常有香客問我筷黔,道長往史,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任佛舱,我火速辦了婚禮椎例,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘请祖。我一直安慰自己订歪,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布损拢。 她就那樣靜靜地躺著陌粹,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上掏秩,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天或舞,我揣著相機(jī)與錄音,去河邊找鬼蒙幻。 笑死映凳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的邮破。 我是一名探鬼主播诈豌,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼抒和!你這毒婦竟也來了矫渔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤摧莽,失蹤者是張志新(化名)和其女友劉穎庙洼,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體镊辕,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡油够,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了征懈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片石咬。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖卖哎,靈堂內(nèi)的尸體忽然破棺而出鬼悠,到底是詐尸還是另有隱情,我是刑警寧澤亏娜,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布厦章,位于F島的核電站,受9級特大地震影響照藻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜汗侵,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一幸缕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧晰韵,春花似錦发乔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至只恨,卻和暖如春译仗,著一層夾襖步出監(jiān)牢的瞬間抬虽,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工纵菌, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留阐污,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓咱圆,卻偏偏與公主長得像笛辟,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子序苏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

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