并發(fā)問(wèn)題的新思路——ThreadLocal

  • ThreadLocal為解決多線程程序的并發(fā)問(wèn)題提供了一種新的思路瞧哟。

  • Synchronized用于線程間的數(shù)據(jù)共享熄赡,而ThreadLocal則用于線程間的數(shù)據(jù)隔離详炬。

從Handler中進(jìn)入ThreadLocal的世界

  • Android中一個(gè)線程只能有一個(gè)Looper姊舵,如果一個(gè)線程已經(jīng)有Looper晰绎,我們?cè)僭诰€程中調(diào)用Looper.prepare()方法會(huì)拋出RuntimeException("Only one Looper may be created per thread")。那如何確保一個(gè)線程中最多只能有一個(gè)Looper呢括丁?我們從Looper中尋找答案荞下。

    public final class Looper {
        
      static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
        
        public static void prepare() {
            prepare(true);
        }
    
        private static void prepare(boolean quitAllowed) {
            //如果該線程已經(jīng)有Looper
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            //該線程中沒(méi)有Looper
            sThreadLocal.set(new Looper(quitAllowed));
        }
        ...
    }
    

    從上面的源碼我們能知道如果一個(gè)線程已經(jīng)有Looper了sThreadLocal.get()便不為空就拋出異常。否則就會(huì)新建一個(gè)Looper然后set進(jìn)入sThreadLocal史飞。

ThreadLocal

  • sThreadLocal是一個(gè)靜態(tài)的成員變量尖昏,所有線程共享它。那它是如何實(shí)現(xiàn)同一個(gè)靜態(tài)變量在不同的線程中調(diào)用get()方法卻能返回不同值的騷操作的呢?讓我們來(lái)看看ThreadLocal的get()set()

    public class ThreadLocal<T> {
        
      public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null)
                    return (T)e.value;
            }
            return setInitialValue();
        }
        
        public void set(T value) {
            Thread t = Thread.currentThread();
            //獲取Thread中的成員變量ThreadLocalMap
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
        
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;
        }
        
        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
        
        private T setInitialValue() {
            //initialValue()返回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;
        }
        ...
    }
    
    public class Thread implements Runnable {
      ...
      ThreadLocal.ThreadLocalMap threadLocals = null;
      ...   
    }
    

    在每個(gè)Thread中都有一個(gè)ThreadLocalMap成員變量,ThreadLocal中的get()set()方法都是通過(guò)獲取到當(dāng)前線程的引用后直接再通過(guò)getMap()方法拿到ThreadMap的引用庐冯。ThreadLocal就是通過(guò)能獲取到每個(gè)線程中的ThreadLocalMap,從而實(shí)現(xiàn)線程間的數(shù)據(jù)隔離迹淌。ThreadLocalMap只能通過(guò)ThreadLocal的createMap()方法初始化河绽。就讓我們來(lái)看看ThreadLocalMap。

ThreadLocalMap

  • ThreadLocalMap是ThreadLocal的內(nèi)部類唉窃。它用來(lái)存儲(chǔ)數(shù)據(jù)耙饰,采用類似hashmap機(jī)制,存儲(chǔ)了以ThreadLocal為key句携,需要隔離的數(shù)據(jù)為value的Entry鍵值對(duì)數(shù)組結(jié)構(gòu)榔幸。里面有一些具體關(guān)于如何清理過(guò)期的數(shù)據(jù)、擴(kuò)容等機(jī)制矮嫉,思路基本和hashmap差不多,有興趣的可以自行閱讀了解牍疏。

    static class ThreadLocalMap {
        ...
        //存儲(chǔ)放入的數(shù)據(jù)
        private Entry[] table;
        
        //Entry繼承ThreadLocal的弱引用
        static class Entry extends WeakReference<ThreadLocal> {
            //要存儲(chǔ)的變量
            Object value;
    
            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
        
        private Entry getEntry(ThreadLocal key) {
            //獲取存儲(chǔ)數(shù)據(jù)的位置
            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);
        }
        
        private void set(ThreadLocal key, Object value) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            //遍歷整個(gè)Entry數(shù)組
            for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
                 ThreadLocal k = e.get();
                 if (k == key) {
                     //覆蓋數(shù)據(jù)
                     e.value = value;
                     return;
                 }
                 if (k == null) {
                     replaceStaleEntry(key, value, i);
                     return;
                 }
            }
            //如果位置為空蠢笋,就新建有要存儲(chǔ)的Entry后放入數(shù)組
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
        ...
    }
    
  • ThreadLocal實(shí)例被線程的ThreadLocalMap實(shí)例持有,也可以看成被線程持有鳞陨。但是ThreadLocalMap的key是ThreadLocal實(shí)例的弱引用昨寞。從而避免了內(nèi)存泄漏。

總結(jié)

  • 每個(gè)線程中都有一個(gè)獨(dú)立的ThreadLocalMap副本厦滤,它所存儲(chǔ)的值援岩,只能被當(dāng)前線程讀取和修改。ThreadLocal類通過(guò)操作每一個(gè)線程特有的ThreadLocalMap副本掏导,從而實(shí)現(xiàn)了變量訪問(wèn)在不同線程中的隔離享怀。每個(gè)Thread只訪問(wèn)自己的 Map,那就不存在多線程寫的問(wèn)題趟咆,也就不需要鎖添瓷。
  • 每個(gè)線程Thread中都有一個(gè)成員變量ThreadLocalMap,ThreadLocalMap底層是一個(gè)數(shù)組實(shí)現(xiàn)的值纱×鄞可以創(chuàng)建不同的ThreadLocal作為Key,存儲(chǔ)對(duì)應(yīng)Value
  • 與同步機(jī)制比較:對(duì)于多線程資源共享的問(wèn)題虐唠,同步機(jī)制采用了“以時(shí)間換空間”的方式搀愧,而ThreadLocal采用了“以空間換時(shí)間”的方式。前者僅提供一份變量疆偿,讓不同的線程排隊(duì)訪問(wèn)咱筛,而后者為每一個(gè)線程都提供了一份變量,因此可以同時(shí)訪問(wèn)而互不影響翁脆。
  • 在很多情況下眷蚓,ThreadLocal比直接使用synchronized同步機(jī)制解決線程安全問(wèn)題更簡(jiǎn)單,更方便反番,且結(jié)果程序擁有更高的并發(fā)性沙热。

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末叉钥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子篙贸,更是在濱河造成了極大的恐慌投队,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爵川,死亡現(xiàn)場(chǎng)離奇詭異敷鸦,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)寝贡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門扒披,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人圃泡,你說(shuō)我怎么就攤上這事碟案。” “怎么了颇蜡?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵价说,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我风秤,道長(zhǎng)鳖目,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任缤弦,我火速辦了婚禮领迈,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘甸鸟。我一直安慰自己惦费,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布抢韭。 她就那樣靜靜地躺著薪贫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪刻恭。 梳的紋絲不亂的頭發(fā)上瞧省,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音鳍贾,去河邊找鬼鞍匾。 笑死,一個(gè)胖子當(dāng)著我的面吹牛骑科,可吹牛的內(nèi)容都是我干的橡淑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼咆爽,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼梁棠!你這毒婦竟也來(lái)了置森?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤符糊,失蹤者是張志新(化名)和其女友劉穎凫海,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體男娄,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡行贪,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了模闲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片建瘫。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖尸折,靈堂內(nèi)的尸體忽然破棺而出暖混,到底是詐尸還是另有隱情,我是刑警寧澤翁授,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站晾咪,受9級(jí)特大地震影響收擦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谍倦,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一塞赂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧昼蛀,春花似錦宴猾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至夫植,卻和暖如春讹剔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背详民。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工延欠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人沈跨。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓由捎,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親饿凛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子狞玛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353