Looper怎么保證每一個(gè)線(xiàn)程的只有一個(gè)的膀跌!

背景

突然被人問(wèn)到這個(gè)問(wèn)題,心想不是很簡(jiǎn)單嗎固灵!但是他又問(wèn)你提到ThreadMap捅伤,哪ThreadMap怎么解決hash沖突的!這還真把我問(wèn)住了巫玻。

原理

抱著虛心學(xué)習(xí)丛忆,不斷遺忘不斷再學(xué)習(xí)的態(tài)度,咱們又來(lái)看一次源碼仍秤。
1熄诡、Looper是怎么創(chuàng)建的
在Android里面最常見(jiàn)的就是我們主線(xiàn)程的Looper了,主線(xiàn)程的Looper是怎么創(chuàng)建徒扶?
我們?cè)趶淖烂纥c(diǎn)擊一個(gè)圖標(biāo),實(shí)際上是通過(guò)launchActivity通過(guò)startActivity啟動(dòng)Activity的根穷,
會(huì)一直走到instrumentation.execStartactivity,然后在里面會(huì)跨進(jìn)程調(diào)用ActivityTaskManager的startActivity姜骡,然后通過(guò)AMS去和Zygote進(jìn)程進(jìn)行socket通信,然后會(huì)foker一個(gè)進(jìn)程屿良,在這個(gè)進(jìn)程里面反射執(zhí)行ActivityThrea的mian方法圈澈。我們來(lái)看看這里有啥
ActiivityThread.java

image.png

我們可以看到很明顯的調(diào)用了


  //初始化Lopper
   Looper.prepareMainLooper()


   //繼續(xù)調(diào)用了Looper的prepare。
    public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

//  我們看到了代碼進(jìn)入ThreadLocal
 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));
    }

在上面的代碼中我們都是通過(guò)sThreadLocal 來(lái)獲取Looper的尘惧,如果已經(jīng)存在了Looper就會(huì)拋出異常康栈。

   //一個(gè)靜態(tài)變量
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

我們繼續(xù)查看它的get()方法

//get 方法也很簡(jiǎn)單,沒(méi)有特別復(fù)雜的操作
   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();
    }

這里由于我們是第一次進(jìn)來(lái)map肯定是為空的,所以我們肯定會(huì)調(diào)用setInitialValue方法
我們來(lái)看看里面有啥

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

這里還是會(huì)去判斷這個(gè)map啥么,我們?nèi)タ纯催@個(gè)map到底是啥登舞。

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

很明顯這個(gè)map就是一個(gè)線(xiàn)程對(duì)象的成員變量,一個(gè)簡(jiǎn)單的變量沒(méi)啥大不了的悬荣,不過(guò)它的類(lèi)型為T(mén)hreadLocalMap菠秒,我們看看這個(gè)TreaLocalMap和普通map區(qū)別。

    static class ThreadLocalMap {

       //我們可以看到他的Key是一個(gè)弱引用氯迂!
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * The initial capacity -- MUST be a power of two 
         * 這里也很好理解践叠,必須為2的倍數(shù)
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary. 
         * 這里是容器
         */
        private Entry[] table;

        /**
         * The number of entries in the table.
         */
        private int size = 0;

        /**
         * The next size value at which to resize.
         */
        private int threshold; // Default to 0

        /**
         * Set the resize threshold to maintain at worst a 2/3 load factor.
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * Increment i modulo len.
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * Decrement i modulo len.
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }
        //一個(gè)構(gòu)造方法
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }
}

和普通map區(qū)別也不大,里面是一個(gè)tab[]用來(lái)保存鍵值對(duì)嚼蚀,不過(guò)這個(gè)Entry的鍵值對(duì)他是一個(gè)弱引用禁灼,需要值得關(guān)注,我們知道GC回收的時(shí)候轿曙,會(huì)對(duì)軟應(yīng)用持有的對(duì)象進(jìn)行一次回收弄捕。所以這里如果key被回收了,value就是無(wú)效的拳芙,但是這個(gè)map又被線(xiàn)程一直持有察藐,所以會(huì)造成內(nèi)存泄露。
了解這個(gè)Map的結(jié)構(gòu)之后我們?cè)賮?lái)看這個(gè)

/////////////////////------ThreadLocal.java-----------------
    private T setInitialValue() {
        T value = initialValue(); // 這里就是一個(gè)null值
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

 //調(diào)用構(gòu)造函數(shù)
   void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

這時(shí)候就直接調(diào)用構(gòu)造函數(shù)舟扎,我們知道了會(huì)構(gòu)造一個(gè)map分飞,并且執(zhí)行一次hash,把value設(shè)置為null睹限。

到這里我們就知道為什么只能保證一個(gè)了譬猫,因?yàn)長(zhǎng)ooper的創(chuàng)建只能是通過(guò)Looper.prepare()或者時(shí)Looper.prepareMain(),就會(huì)給調(diào)用這個(gè)方法的線(xiàn)程創(chuàng)建一個(gè)Looper羡疗,當(dāng)你想在創(chuàng)建的時(shí)候會(huì)判斷當(dāng)前線(xiàn)程對(duì)象的變量ThreadLocalMap染服,是否包含了Looper的Value,如果存在會(huì)拋出異常叨恨。這里的key就是sTrheadLocal柳刮。
這時(shí)候我們?cè)倩卮餞hreadLocalMap怎么解決hash沖突的?

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

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

可以清楚的看到源碼里面是通過(guò)線(xiàn)性探測(cè)來(lái)解決的痒钝。不是很麻煩秉颗。
一般解決hash沖突有三種方法:線(xiàn)性探測(cè),再hash送矩,以及鏈表法蚕甥。

小貼士

線(xiàn)性探測(cè)是解決hash沖突的一種方式,比如一個(gè)key 的hash值為21栋荸,一個(gè)key的hash值為11菇怀,他們?cè)趯?duì)數(shù)組長(zhǎng)度11 (key.threadLocalHashCode & (len-1);)取模的時(shí)候凭舶,都會(huì)定位到1這個(gè)位置。
比如先put ke為21這個(gè)發(fā)現(xiàn)1這個(gè)位置沒(méi)有值爱沟,直接就放入了帅霜,這時(shí)候我們?cè)俜湃?1這個(gè)key,他會(huì)發(fā)現(xiàn)1這個(gè)位置已經(jīng)有值了钥顽,他會(huì)將下標(biāo)+1义屏,變?yōu)?這個(gè)位置,沒(méi)有值蜂大,直接放入闽铐。
我們從里面取出元素的時(shí)候,比如我們先get 11這個(gè)key奶浦,直接定位到1這個(gè)位置兄墅,發(fā)現(xiàn)這時(shí)候我們不能直接獲取value,先要比對(duì)equal方法澳叉,相等直接返回value隙咸,否則需要將下標(biāo)加1比對(duì)下一個(gè)位置。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末成洗,一起剝皮案震驚了整個(gè)濱河市五督,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌瓶殃,老刑警劉巖充包,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異遥椿,居然都是意外死亡基矮,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)冠场,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)家浇,“玉大人,你說(shuō)我怎么就攤上這事碴裙「直” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵舔株,是天一觀的道長(zhǎng)莺琳。 經(jīng)常有香客問(wèn)我,道長(zhǎng)督笆,這世上最難降的妖魔是什么芦昔? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任诱贿,我火速辦了婚禮娃肿,結(jié)果婚禮上咕缎,老公的妹妹穿的比我還像新娘。我一直安慰自己料扰,他們只是感情好凭豪,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著晒杈,像睡著了一般嫂伞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拯钻,一...
    開(kāi)封第一講書(shū)人閱讀 49,821評(píng)論 1 290
  • 那天帖努,我揣著相機(jī)與錄音,去河邊找鬼粪般。 笑死拼余,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的亩歹。 我是一名探鬼主播匙监,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼小作!你這毒婦竟也來(lái)了亭姥?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤顾稀,失蹤者是張志新(化名)和其女友劉穎达罗,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體础拨,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡氮块,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了诡宗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片滔蝉。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖塔沃,靈堂內(nèi)的尸體忽然破棺而出蝠引,到底是詐尸還是另有隱情,我是刑警寧澤蛀柴,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布螃概,位于F島的核電站,受9級(jí)特大地震影響鸽疾,放射性物質(zhì)發(fā)生泄漏吊洼。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一制肮、第九天 我趴在偏房一處隱蔽的房頂上張望冒窍。 院中可真熱鬧递沪,春花似錦、人聲如沸综液。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)谬莹。三九已至檩奠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間附帽,已是汗流浹背埠戳。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蕉扮,地道東北人乞而。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像慢显,于是被迫代替她去往敵國(guó)和親爪模。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349

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

  • App啟動(dòng)涉及三個(gè)進(jìn)程和六個(gè)大類(lèi): 三個(gè)進(jìn)程: Launcher進(jìn)程:整個(gè)App啟動(dòng)流程的起點(diǎn)荚藻,負(fù)責(zé)接收用戶(hù)點(diǎn)擊屏...
    耿之偉閱讀 266評(píng)論 0 1
  • 簡(jiǎn)介: 在android系統(tǒng)中屋灌,Home界面也就是Launcher的界面,Launcher它本身也是一個(gè)應(yīng)用程序应狱,...
    Memebox閱讀 2,446評(píng)論 4 19
  • 本文中的部分內(nèi)容分析的是舊版本的代碼共郭,與Android 9.0有所出入,所以寫(xiě)了一篇完整的9.0應(yīng)用啟動(dòng)流程分析疾呻,...
    酷酷的Demo閱讀 1,319評(píng)論 0 5
  • 面試必背 會(huì)舍棄除嘹、總結(jié)概括——根據(jù)我這些年面試和看面試題搜集過(guò)來(lái)的知識(shí)點(diǎn)匯總而來(lái) 建議根據(jù)我的寫(xiě)的面試應(yīng)對(duì)思路中的...
    luoyangzk閱讀 6,745評(píng)論 6 173
  • 推薦指數(shù): 6.0 書(shū)籍主旨關(guān)鍵詞:特權(quán)、焦點(diǎn)岸蜗、注意力尉咕、語(yǔ)言聯(lián)想、情景聯(lián)想 觀點(diǎn): 1.統(tǒng)計(jì)學(xué)現(xiàn)在叫數(shù)據(jù)分析璃岳,社會(huì)...
    Jenaral閱讀 5,705評(píng)論 0 5