ThreadLocal的作用和實現(xiàn)原理

ThreadLocal的作用

ThreadLocal是一個線程內(nèi)部的數(shù)據(jù)存儲類,通過它可以在指定的線程中存儲數(shù)據(jù)浆兰,數(shù)據(jù)存儲以后磕仅,只有在指定的線程中可以獲取到存儲的數(shù)據(jù),對于其他線程來說則無法取到數(shù)據(jù)簸呈。

ThreadLocal的主要作用

輕松實現(xiàn)一些看起來很復雜的功能榕订,適合以下一些應用場景。

  • 應用場景1

某些數(shù)據(jù)是以線程為作用域并且不同線程具有不同的數(shù)據(jù)的副本時蜕便,就可以考慮用ThreadLocal劫恒。

例如:Android中,Handler需要獲取當前線程的Looper轿腺,很顯然Looper的作用域是線程并且不同線程具有不同的Looper两嘴。

通過ThreadLocal就可以輕松實現(xiàn)Looper在線程中的存取。

  • 應用場景2

復雜邏輯下的對象傳遞吃溅,比如監(jiān)聽器的傳遞溶诞,有些時候一個線程中的任務過于復雜,我們又需要監(jiān)聽器能夠貫穿整個線程的執(zhí)行過程决侈。

采用ThreadLocal可以讓監(jiān)聽器作為線程內(nèi)的全局對象而存在,在線程內(nèi)部只要通過get方法就可以獲取到監(jiān)聽器。

ThreadLocal的使用示例

ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();

mBooleanThreadLocal.set(true);
Log.d("@@@", "[Thread@main]mBooleanThreadLocal = " + mBooleanThreadLocal.get());

new Thread("Thread@1") {
    @Override
    public void run() {
        mBooleanThreadLocal.set(false);
        Log.d("@@@", "[Thread@1]mBooleanThreadLocal = " + mBooleanThreadLocal.get());
    }
}.start();

new Thread("Thread@2") {
    @Override
    public void run() {
        Log.d("@@@", "[Thread@2]mBooleanThreadLocal = " + mBooleanThreadLocal.get());
    }
}.start();

輸出的日志如下

D/@@@: [Thread@main]mBooleanThreadLocal = true
D/@@@: [Thread@1]mBooleanThreadLocal = false
D/@@@: [Thread@2]mBooleanThreadLocal = null

在上面示例代碼中赖歌,主線程的mBooleanThreadLocal的值設置為true枉圃,子線程1的mBooleanThreadLocal的值設置為false,子線程2的mBooleanThreadLocal的值不設置庐冯。

從日志可以看出孽亲,雖然在不同線程中訪問同一個ThreadLocal對象,但是他們通過ThreadLocal獲取到值卻是不一樣的展父,這就是ThreadLocal的神奇之處返劲。

ThreadLocal的實現(xiàn)原理

ThreadLocal是一個泛型類,定義為public class ThreadLocal<T>栖茉,只要弄清楚ThreadLocal的get方法和set方法篮绿,就可以明白它的實現(xiàn)原理。

ThreadLocal的set方法(Android API 26)吕漂,源碼如下

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

從set源碼可以看出亲配,首先getMap方法來獲取當前線程的ThreadLocalMap,這個Map是一個自定義的hash map惶凝,key是TheadLocal吼虎,value是對應存儲的值。

ThreadLocal的get方法(Android API 26)苍鲜,源碼如下

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源碼可以看出思灰,首先也是一樣用getMap方法來獲取當前線程的ThreadLocalMap,然后根據(jù)key=當前ThreadLocal來獲取對應的value值混滔。

從ThreadLocal的set和get方法可以看出洒疚,它們所操作的都是當前線程的ThreadLocalMap對象。

因此在不同線程中遍坟,訪問同一個ThreadLocal的set和get方法拳亿,它們對ThreadLocalde的讀、寫操作僅限于各自線程的內(nèi)部愿伴,從而使ThreadLocal可以在多個線程中互不干擾地存儲和修改數(shù)據(jù)肺魁。

ThreadLocal在Android中的應用

獲取當前線程的Looper

在Looper類中,定義了一個ThreadLocal靜態(tài)常量

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

1隔节、初始化Looper鹅经,即調(diào)用Looper.perpar(),源碼如下

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));
}

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

從源碼可以看到怎诫,在一個線程中初始化Looper瘾晃,是用ThreadLocal存儲Looper對象。同時保證一個線程只擁有一個Looper幻妓,否則在初始化會報錯
Only one Looper may be created per thread

2蹦误、獲取當前線程的Looper,即Looper.myLooper(),源碼如下

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

從源碼可以看到强胰,返回Looper對象是調(diào)用ThreadLocal.get()舱沧,即當前線程對應的Looper對象。

我們可以根據(jù)此偶洋,判斷當前線程是否是主線程

public boolean isMainThread() {
    return Looper.myLooper() == Looper.getMainLooper();
}

另外熟吏,Handler也用來判斷當前線程是否有Looper,否則報錯Can't create handler inside thread that has not called Looper.prepare()

mLooper = Looper.myLooper();
if (mLooper == null) {
    throw new RuntimeException(
        "Can't create handler inside thread that has not called Looper.prepare()");
}

參考
任玉剛的《Android開發(fā)藝術探索》

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末玄窝,一起剝皮案震驚了整個濱河市牵寺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌恩脂,老刑警劉巖帽氓,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異东亦,居然都是意外死亡杏节,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人棘利,你說我怎么就攤上這事普筹。” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我玄渗,道長,這世上最難降的妖魔是什么狸眼? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任藤树,我火速辦了婚禮,結果婚禮上拓萌,老公的妹妹穿的比我還像新娘岁钓。我一直安慰自己,他們只是感情好微王,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布屡限。 她就那樣靜靜地躺著,像睡著了一般炕倘。 火紅的嫁衣襯著肌膚如雪钧大。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天罩旋,我揣著相機與錄音啊央,去河邊找鬼眶诈。 笑死,一個胖子當著我的面吹牛劣挫,可吹牛的內(nèi)容都是我干的册养。 我是一名探鬼主播东帅,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼压固,長吁一口氣:“原來是場噩夢啊……” “哼靠闭!你這毒婦竟也來了帐我?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤愧膀,失蹤者是張志新(化名)和其女友劉穎拦键,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體檩淋,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡芬为,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了蟀悦。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片媚朦。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖日戈,靈堂內(nèi)的尸體忽然破棺而出询张,到底是詐尸還是另有隱情,我是刑警寧澤浙炼,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布份氧,位于F島的核電站,受9級特大地震影響弯屈,放射性物質(zhì)發(fā)生泄漏蜗帜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一资厉、第九天 我趴在偏房一處隱蔽的房頂上張望厅缺。 院中可真熱鬧,春花似錦酌住、人聲如沸店归。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽消痛。三九已至,卻和暖如春都哭,著一層夾襖步出監(jiān)牢的瞬間秩伞,已是汗流浹背逞带。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留纱新,地道東北人展氓。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像脸爱,于是被迫代替她去往敵國和親遇汞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

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