ThreadLocal簡(jiǎn)介

ThreadLocal簡(jiǎn)介

Java中的ThreadLocal類給每個(gè)線程分配一個(gè)只屬于該線程的變量副本欠窒,可以用來實(shí)現(xiàn)線程間的數(shù)據(jù)隔離悦污,當(dāng)前線程的變量不能被其他線程訪問铸屉。

ThreadLocal使用

創(chuàng)建ThreadLocal變量

private ThreadLocal myThreadLocal = new ThreadLocal();

訪問ThreadLocal變量

設(shè)置需要保存的值:
myThreadLocal.set("ThreadLocal value");

讀取保存在ThreadLocal變量中的值:
String threadLocalVlaue = (String) myThreadLocal.get();

ThreadLocal范型

private ThreadLocal myThreadLocal = new ThreadLocal<String>()

初始化ThreadLocal的值

private ThreadLocal myThreadLocal = new ThreadLocal<String>(){
    protected String initialVlaue(){
        return "initial value";
    }
};

源碼分析

源碼版本:

jdk7u80

最常用的方法就是get和set方法,所以先從這兩個(gè)方法入手切端,分析下使用彻坛。

set(T value)

將當(dāng)前的線程局部變量的副本的值設(shè)置為指定的值。子類一般不需要重寫該方法踏枣,只需要使用initialValue方法去設(shè)置初始值昌屉。

public void set(T value) {
    //獲取當(dāng)前線程
    Thread t = Thread.currentThread();
    //從當(dāng)前線程中得到當(dāng)前線程的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null)
        //不為空的話,調(diào)用ThreadLocalMap的set方法設(shè)置值
        map.set(this, value);
    else
        //ThreadLocalMap為null茵瀑,還沒有被初始化间驮,創(chuàng)建新的map
        createMap(t, value);
}

getMap(Thread t)

獲取指定的線程t的ThreadLocalMap

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

createMap(t, value)

為當(dāng)前線程t初始化一個(gè)ThreadLocalMap用來存儲(chǔ)值,初始值是value马昨。

在Thread類中ThreadLocal.ThreadLocalMap threadLocals = null;是用來存儲(chǔ)當(dāng)前線程對(duì)應(yīng)的ThreadLocalMap竞帽,屬于線程私有的。所以createMap方法使用t.threadLocals = new ThreadLocalMap(this, firstValue);來設(shè)置鸿捧。

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

ThreadLocal用來把變量的副本存儲(chǔ)到線程中屹篓,變量的副本就只能是當(dāng)前線程私有,而在線程中是通過ThreadLocalMap來存儲(chǔ)副本的匙奴,所以有必要了解下ThreadLocalMap是怎么實(shí)現(xiàn)的堆巧。

ThreadLocalMap

ThreadLocalMap是一個(gè)自定義的HashMap,用來存儲(chǔ)線程本地變量的值泼菌,類似與Map谍肤。ThreadLocalMap內(nèi)部是使用Entry對(duì)象來存儲(chǔ)。

Entry

Entry繼承了WeakReference哗伯,使用ThreadLocal作為key谣沸,value為ThreadLocal的值。

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;ThreadLocalMap的初始容量為16笋颤。

private Entry[] table;存放線程本地變量的數(shù)組。

private int size = 0; 線程本地變量的數(shù)目。

private int threshold 擴(kuò)容的閾值伴澄。

擴(kuò)容的閾值為指定長(zhǎng)度的三分之二

private void setThreshold(int len) {
    threshold = len * 2 / 3;
}

//構(gòu)造方法赋除,當(dāng)我們第一次使用的時(shí)候會(huì)構(gòu)造一個(gè)新的ThreadLocalMap

ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
    //存放線程本地變量的數(shù)組,初始容量16
    table = new Entry[INITIAL_CAPACITY];
    //得到存放Entry的數(shù)組下標(biāo)
    int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    //在得出的位置處新建一個(gè)Entry對(duì)象
    table[i] = new Entry(firstKey, firstValue);
    //大小設(shè)為1
    size = 1;
    //設(shè)置閾值 16*2/3
    setThreshold(INITIAL_CAPACITY);
}

使用給定的父map來構(gòu)造一個(gè)ThreadLocalMap

private ThreadLocalMap(ThreadLocalMap parentMap) {
    //父map中存放的線程本地變量數(shù)據(jù)
    Entry[] parentTable = parentMap.table;
    //父map的長(zhǎng)度
    int len = parentTable.length;
    //設(shè)置閾值
    setThreshold(len);
    //新建長(zhǎng)度為len的Entry數(shù)組
    table = new Entry[len];
    //循環(huán)把父map的數(shù)組的元素放到新數(shù)組中去非凌,中間需要重新計(jì)算數(shù)組下標(biāo)举农。
    for (int j = 0; j < len; j++) {
        Entry e = parentTable[j];
        if (e != null) {
            ThreadLocal key = e.get();
            if (key != null) {
                Object value = key.childValue(e.value);
                Entry c = new Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null)
                    h = nextIndex(h, len);
                table[h] = c;
                size++;
            }
        }
    }
}

根據(jù)key獲取Entry

private Entry getEntry(ThreadLocal key) {
    //計(jì)算數(shù)組下標(biāo)
    int i = key.threadLocalHashCode & (table.length - 1);
    //獲取元素
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        //沒有找到key的時(shí)候的處理
        return getEntryAfterMiss(key, i, e);
}

//當(dāng)沒有找到對(duì)應(yīng)的key時(shí)候

//key ThreadLocal對(duì)象
//i 計(jì)算出來的數(shù)組下標(biāo)
//e 在i處的entry
private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;

    while (e != null) {
        //獲取key
        ThreadLocal k = e.get();
        //找到key,返回e
        if (k == key)
            return e;
        //key為null敞嗡,找不到颁糟,清除掉
        if (k == null)
            expungeStaleEntry(i);
        else
            //key不為null,計(jì)算下一個(gè)數(shù)組下標(biāo)
            i = nextIndex(i, len);
        //返回下一個(gè)entry
        e = tab[i];
    }
    return null;
}

//存放指定的key和value

private void set(ThreadLocal key, Object value) {

    //當(dāng)前存放的數(shù)組
    Entry[] tab = table;
    //數(shù)組長(zhǎng)度
    int len = tab.length;
    //根據(jù)key獲取存放的數(shù)組下標(biāo)
    int i = key.threadLocalHashCode & (len-1);
    
    //從第i個(gè)元素開始挨個(gè)遍歷
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
         //獲取到i處的key
        ThreadLocal k = e.get();
    //i處的key和要存放的key相等喉悴,將原來的值替換成新的值棱貌,返回。
        if (k == key) {
            e.value = value;
            return;
        }
    //i處key為null
        if (k == null) {
            //替換原來的Entry
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    //不存在key箕肃,新建一個(gè)Entry
    tab[i] = new Entry(key, value);
    //size加1
    int sz = ++size;
    //不能移除一些舊的entry并且新的size已經(jīng)大于等于閾值了婚脱,需要重新擴(kuò)容
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

//rehash()

private void rehash() {
    //首先清除舊的entry
    expungeStaleEntries();

    // size大于等于閾值的四分之三,將容量擴(kuò)展為兩倍
    if (size >= threshold - threshold / 4)
        resize();
}

ThreadLocalMap內(nèi)部的Entry的get和set基本就這些勺像,接下來繼續(xù)看ThreadLocal的get方法

public T get()

public T get() {
    //獲取當(dāng)前線程
    Thread t = Thread.currentThread();
    //獲取當(dāng)前線程的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    //map為空障贸,設(shè)置初始值,并返回
    return setInitialValue();
}

private T setInitialValue()

private T setInitialValue() {
    //這里初始值為null
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

remove()

移除當(dāng)前線程的ThreadLocalMap中的值

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

總結(jié)一下get和set方法

get方法:

首先獲取到當(dāng)前線程吟宦,然后獲取當(dāng)前線程內(nèi)部的ThreadLocalMap篮洁,如果map不為空,就查找到Entry中的值殃姓,返回袁波;如果map為空,設(shè)置初始值辰狡,并返回锋叨。

set方法:

首先獲取到當(dāng)前線程,然后獲取當(dāng)前線程內(nèi)部的ThreadLocalMap宛篇,如果map不為空娃磺,直接使用Entry的set設(shè)置值,此方法會(huì)替換原來的值叫倍;如果map為空偷卧,說明沒有使用過,新建一個(gè)map并使用當(dāng)前線程和指定的值初始化吆倦。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末听诸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蚕泽,更是在濱河造成了極大的恐慌晌梨,老刑警劉巖桥嗤,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異仔蝌,居然都是意外死亡泛领,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門敛惊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來渊鞋,“玉大人,你說我怎么就攤上這事瞧挤∥危” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵特恬,是天一觀的道長(zhǎng)执俩。 經(jīng)常有香客問我,道長(zhǎng)鸵鸥,這世上最難降的妖魔是什么奠滑? 我笑而不...
    開封第一講書人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮妒穴,結(jié)果婚禮上宋税,老公的妹妹穿的比我還像新娘。我一直安慰自己讼油,他們只是感情好杰赛,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著矮台,像睡著了一般乏屯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上瘦赫,一...
    開封第一講書人閱讀 51,718評(píng)論 1 305
  • 那天辰晕,我揣著相機(jī)與錄音,去河邊找鬼确虱。 笑死含友,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的校辩。 我是一名探鬼主播窘问,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼宜咒!你這毒婦竟也來了惠赫?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤故黑,失蹤者是張志新(化名)和其女友劉穎儿咱,沒想到半個(gè)月后庭砍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡概疆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年逗威,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片岔冀。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖概耻,靈堂內(nèi)的尸體忽然破棺而出使套,到底是詐尸還是另有隱情,我是刑警寧澤鞠柄,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布侦高,位于F島的核電站,受9級(jí)特大地震影響厌杜,放射性物質(zhì)發(fā)生泄漏奉呛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一夯尽、第九天 我趴在偏房一處隱蔽的房頂上張望瞧壮。 院中可真熱鬧,春花似錦匙握、人聲如沸咆槽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽秦忿。三九已至,卻和暖如春蛾娶,著一層夾襖步出監(jiān)牢的瞬間灯谣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工蛔琅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留胎许,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓揍愁,卻偏偏與公主長(zhǎng)得像呐萨,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子莽囤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

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