ThreadLocal源碼學(xué)習(xí)

線程本地變量:線程本地變量通常是一個類中的私有靜態(tài)的成員變量殉簸。我們可以在不同的線程中的調(diào)用線程本地變量的getset方法,來獲取和設(shè)置當(dāng)前線程中線程本地變量的值。在當(dāng)前線程改變線程本地變量的值,并不會影響其他線程本地變量的值踩娘。

上面一段話有點抽象,先舉個例子說一下喉祭。

public class ThreadLocalTest {

    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        //注釋1處养渴,在主線程中設(shè)置ThreadLocal保存的變量值
        threadLocal.set("初始名稱");
       //注釋2處
        System.out.println(Thread.currentThread().getName() + " ," + threadLocal.get());
        new TestThread("線程甲", threadLocal).start();
        new TestThread("線程乙", threadLocal).start();
    }
}

class TestThread extends Thread {

    private ThreadLocal<String> threadLocal;

    public TestThread(String name, ThreadLocal<String> threadLocal) {
        super(name);
        this.threadLocal = threadLocal;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if (i == 6) {
                //當(dāng)i==6的時候替換成當(dāng)前線程名
                threadLocal.set(getName());
            }
            //獲取
            System.out.println(Thread.currentThread().getName() + " ," + threadLocal.get() + ",i= " + i);
        }
    }
}

輸出如下

main ,初始名稱
線程甲 ,null泛烙,i= 0
線程甲 ,null厚脉,i= 1
線程甲 ,null,i= 2
線程甲 ,null胶惰,i= 3
線程甲 ,null,i= 4
線程甲 ,null霞溪,i= 5
線程甲 ,線程甲孵滞,i= 6
線程甲 ,線程甲,i= 7
線程甲 ,線程甲鸯匹,i= 8
線程甲 ,線程甲坊饶,i= 9
線程乙 ,null,i= 0
線程乙 ,null殴蓬,i= 1
線程乙 ,null匿级,i= 2
線程乙 ,null,i= 3
線程乙 ,null染厅,i= 4
線程乙 ,null痘绎,i= 5
線程乙 ,線程乙,i= 6
線程乙 ,線程乙肖粮,i= 7
線程乙 ,線程乙孤页,i= 8
線程乙 ,線程乙,i= 9

在這個例子中涩馆,我們創(chuàng)建了一個ThreadLocal變量行施,內(nèi)部保存的是一個String類型的變量允坚。

private static ThreadLocal<String> name = new ThreadLocal<>();

在main方法的注釋1處,我們在主線程中設(shè)置ThreadLocal變量值為初始名稱蛾号,所以在注釋2處輸出如下

main ,初始名稱

然后我們新建了兩個線程稠项,在線程的run方法中首先從0到5threadLocal.get()方法獲取的值都是null。
然后從6開始把threadLocal設(shè)置為當(dāng)前線程的名字鲜结,然后再調(diào)用threadLocal.get()方法輸出的就是線程對應(yīng)的名字了展运。

源碼分析

首先我們看一下ThreadLocal的構(gòu)造方法

public ThreadLocal() {
    
}

看下ThreadLocal的set(T value)方法相關(guān)操作

public void set(T value) {
    Thread t = Thread.currentThread();
    //注釋1處
    ThreadLocalMap map = getMap(t);
    //注釋2處,注意傳入的this是作為key的轻腺。
    if (map != null)
        map.set(this, value);
    //注釋3處乐疆,
    else
        createMap(t, value);
}

在注釋1處,首先獲取當(dāng)前線程的ThreadLocalMap對象贬养。ThreadLocal的getMap方法挤土。

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

Thread類的threadLocals變量是一個ThreadLocal.ThreadLocalMap對象,用來保存與當(dāng)前線程相關(guān)的ThreadLocal變量误算。

public class Thread implements Runnable {
    //...
    //用來保存與當(dāng)前線程相關(guān)的ThreadLocal變量
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

ThreadLocal.ThreadLocalMap是ThreadLocal的一個靜態(tài)內(nèi)部類仰美,只適合用來保存線程本地變量。ThreadLocalMap的實體使用弱引用來保存key儿礼。注意key是ThreadLocal<?> 咖杂。

static class ThreadLocalMap {
    
    //初始容量,必須是2的冪
    private static final int INITIAL_CAPACITY = 16;
    //存儲entry的表蚊夫,根據(jù)需要調(diào)整大小诉字。表的長度必須是2的冪
    private Entry[] table;
    //...
    
    static class Entry extends WeakReference<ThreadLocal<?>> {
        //和ThreadLocal關(guān)聯(lián)的值 
        Object value;
    
        Entry(ThreadLocal<?> k, Object v) {
            //使用弱引用來保存key
            super(k);
            value = v;
        }
    }
}

set(T value)方法的注釋3處,如果獲取到的map對象為null知纷,則調(diào)用createMap方法壤圃。

創(chuàng)建一個ThreadLocalMap對象賦值給當(dāng)前線程的threadLocals變量。同時保存了初始的key和value琅轧。注意key是ThreadLocal類型的

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocalMap的兩個參數(shù)的構(gòu)造方法

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    //注釋1處伍绳,看一看 threadLocalHashCode 是怎么計算的。
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    //保存初始的key和value
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}

注釋1處乍桂,threadLocalHashCode 是一個final類型的變量冲杀。而且是一個原子類型的變量。

private static AtomicInteger nextHashCode =
        new AtomicInteger();
private final int threadLocalHashCode = nextHashCode();
private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

set(T value)方法的注釋2處睹酌,如果獲取到的map對象不為null权谁,則調(diào)用ThreadLocalMap的set(ThreadLocal<?> key, Object value)方法。

ThreadLocalMap的set(ThreadLocal<?> key, Object value)方法

private void set(ThreadLocal<?> key, Object value) {

    Entry[] tab = table;
    int len = tab.length;
    //注釋1處憋沿,獲取在map中的位置
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        //更新key對應(yīng)的value
        if (k == key) {
            e.value = value;
            return;
        }
        //替換指定位置上的鍵值對
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
   //加入到table中
    tab[i] = new Entry(key, value);
    int sz = ++size;
    //是否需要縮減table或者擴容闯传,如果是擴容的話,容量會增加到兩倍
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

set(ThreadLocal<?> key, Object value)方法,注釋1處甥绿,獲取在map中的位置字币,要注意一下這個key,threadLocalHashCode是一個final 類型的變量共缕,在第一次創(chuàng)建ThreadLocal對象的時候初始化洗出,后面就不會變了。

接下來看一下ThreadLocal的get方法

public T get() {
    //獲取當(dāng)前線程
    Thread t = Thread.currentThread();
    //獲取當(dāng)前線程的ThreadLocalMap對象map
    ThreadLocalMap map = getMap(t);
    if (map != null) {//如果map存在图谷,返回對應(yīng)的值
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            T result = (T)e.value;
            return result;
        }
    }
    //map不存在翩活。調(diào)用setInitialValue方法
    return setInitialValue();
}

方法內(nèi)部判斷如果線程的threadLocals不為null,就從中取出對應(yīng)的值并返回便贵。

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

如果map中沒有對應(yīng)的值菠镇,返回setInitialValue方法的執(zhí)行結(jié)果。

ThreadLocal的setInitialValue方法

private T setInitialValue() {
    //注釋1處,首先調(diào)用initialValue方法
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

ThreadLocal的initialValue方法默認返回null

protected T initialValue() {
    return null;
}

我們可以重寫ThreadLocal的initialValue方法來提供一個默認值承璃,就像這樣利耍。

private static ThreadLocal<String> name = new ThreadLocal<String>(){

    @Override
    protected String initialValue() {
       return "hello world";
    }
};

總結(jié):

  1. 實際的通過ThreadLocal創(chuàng)建的副本是存儲在每個線程自己的threadLocals中的。
  2. 如果想在調(diào)用get方法之前不需要調(diào)用set方法就想得到一個默認的值的話盔粹,可以重寫initialValue()方法隘梨。

Android 中的Looper 是使用ThreadLocal來保存的。

public static void prepare() {
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

參考鏈接:

  1. Java并發(fā)編程:深入剖析ThreadLocal
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末舷嗡,一起剝皮案震驚了整個濱河市轴猎,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌进萄,老刑警劉巖捻脖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異中鼠,居然都是意外死亡郎仆,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門兜蠕,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抛寝,你說我怎么就攤上這事熊杨。” “怎么了盗舰?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵晶府,是天一觀的道長。 經(jīng)常有香客問我钻趋,道長川陆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任蛮位,我火速辦了婚禮较沪,結(jié)果婚禮上鳞绕,老公的妹妹穿的比我還像新娘。我一直安慰自己尸曼,他們只是感情好们何,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著控轿,像睡著了一般冤竹。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上茬射,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天鹦蠕,我揣著相機與錄音,去河邊找鬼在抛。 笑死钟病,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的霜定。 我是一名探鬼主播档悠,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼望浩!你這毒婦竟也來了辖所?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤磨德,失蹤者是張志新(化名)和其女友劉穎缘回,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體典挑,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡酥宴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了您觉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拙寡。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖琳水,靈堂內(nèi)的尸體忽然破棺而出肆糕,到底是詐尸還是另有隱情,我是刑警寧澤在孝,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布诚啃,位于F島的核電站,受9級特大地震影響私沮,放射性物質(zhì)發(fā)生泄漏始赎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望造垛。 院中可真熱鬧魔招,春花似錦、人聲如沸筋搏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽奔脐。三九已至俄周,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間髓迎,已是汗流浹背峦朗。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留排龄,地道東北人波势。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像橄维,于是被迫代替她去往敵國和親尺铣。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

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