Android消息機(jī)制-ThreadLocal原理解析:數(shù)據(jù)存取

說起Android消息機(jī)制,老生常談的問題,Handler ,Looper,MessageQueue,Message這幾個(gè)都是離不開的話題,不同的類承載不同的功能,首先還是簡(jiǎn)單的總結(jié)下各自的功能:

Handler:發(fā)送和處理消息
MessageQueue:消息隊(duì)列,采用單鏈表結(jié)構(gòu)存儲(chǔ)數(shù)據(jù)
Looper:調(diào)用loop()輪詢消息
Message:需要發(fā)送的內(nèi)容,消息


畫一張流程圖顯示:
handler1.png

當(dāng)然上邊只是簡(jiǎn)單的分析了下 Handler一個(gè)發(fā)送和處理的一個(gè)流程,只是讓大家溫故下,下面正式的開始介紹這個(gè)ThreadLocal 重量級(jí)角色:

ThreadLocal 介紹:(針對(duì)JDK 1.7闡述)

定義:早在JDK 1.2的版本中就提供java.lang.ThreadLocal疤祭,ThreadLocal為解決多線程程序的并發(fā)問題提供了一種新的思路缓呛。使用這個(gè)工具類可以很簡(jiǎn)潔地編寫出優(yōu)美的多線程程序,百度百科一下就知道,當(dāng)然這只是定義,先上一段代碼瞧瞧:

private ThreadLocal<Boolean> booleanThreadLocal = new ThreadLocal<>();..

button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                booleanThreadLocal.set(true);
                booleanThreadLocal.set(false);
                Log.i(TAG, "run: ThreadName-------->"+Thread.currentThread().getName()+booleanThreadLocal.get());
                new Thread("Thread1"){
                    @Override
                    public void run() {
                        booleanThreadLocal.set(true);
                        Log.i(TAG, "run: Thread1+ThreadName-------->"+Thread.currentThread().getName()+booleanThreadLocal.get());
                    }
                }.start();
                new Thread("Thread2"){
                    @Override
                    public void run() {
                        Log.i(TAG, "run: Thread2+ThreadName-------->"+Thread.currentThread().getName()+booleanThreadLocal.get());
                    }
                }.start();
            }
        });

代碼很簡(jiǎn)單就是給一個(gè)button設(shè)置一個(gè)點(diǎn)擊監(jiān)聽事件,然后通過獲取ThreadLocal的get方法分別獲取里面值:我們來看打印值(界面過于簡(jiǎn)單,就不在展示):

11-24 06:35:23.683 13691-13691/com.example.administrator.dbhelp I/MainActivity: run: ThreadName-------->main---->false
11-24 06:35:23.693 13691-13785/com.example.administrator.dbhelp I/MainActivity: run: Thread2+ThreadName-------->Thread2---->null
11-24 06:35:23.694 13691-13784/com.example.administrator.dbhelp I/MainActivity: run: Thread1+ThreadName-------->Thread1---->true

我在不同的線程采用相同的對(duì)象調(diào)用他們的get方法,但是他們打印的值卻是不一樣,很奇妙吧,這也就是他的奇妙之處.
首先簡(jiǎn)答的介紹為什么會(huì)產(chǎn)生這種效果,方便后邊更好的看代碼,ThreadLocal 內(nèi)部維護(hù)了一個(gè)針對(duì)每一個(gè)線程的數(shù)組Entry[],它的初始容量是16,我們?cè)谠O(shè)置value的時(shí)候?qū)?dāng)前的value值封裝Entry類里面,在然后再根據(jù)當(dāng)前的ThreadLocal的索引去查中對(duì)應(yīng)的Entry值,最終根據(jù)Entry對(duì)象取出value值,很明顯每個(gè)線程的數(shù)組是不相同,所以就可以取出不同的Entry,這么說肯定還是不是特別明白,沒關(guān)系這個(gè)只是開胃菜,提前有個(gè)了解有印象就行;上代碼一看就明了:

set:

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

邏輯很簡(jiǎn)單,創(chuàng)建當(dāng)前的線程根據(jù)當(dāng)前的線程獲取ThreadLocalMap 實(shí)例對(duì)象,這個(gè)ThreadLocalMap 是個(gè)什么了看這個(gè)跟HashMap 很相似啊,可別只看表面,這個(gè)可是沒有一點(diǎn)親戚關(guān)系,這點(diǎn)可別誤解了,接著看得到當(dāng)前線程的map 第一次獲取肯定是空:
我們看下getMap做了什么:

Thread 屬性;
ThreadLocal.ThreadLocalMap threadLocals = null;

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

找到這個(gè)變量第一次肯定是空, createMap(t, value);接著看

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

變量賦值操作,看下ThreadLocalMap的構(gòu)造:

  private static final int INITIAL_CAPACITY = 16;
 table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);

很簡(jiǎn)單的吧,首先構(gòu)造一個(gè)數(shù)組也就是我上邊提到的Entry數(shù)組初始容量16,然后通過按位運(yùn)算得到一個(gè)變量i值,構(gòu)造一個(gè)Entry對(duì)象將ThreadLocal和Value放進(jìn)去,將設(shè)置進(jìn)去的值包裝成一個(gè)對(duì)象存進(jìn)數(shù)組里邊,獲取的時(shí)候在通過數(shù)組的index取出當(dāng)前的對(duì)象,這樣一分析是不是有種明白的感覺別急,接著看構(gòu)造;

static class Entry extends WeakReference<ThreadLocal> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

這個(gè)實(shí)體繼承WeakReference(就是我們常說的弱引用) v就是我們?cè)O(shè)置進(jìn)去的value,現(xiàn)在整體的流程是不是很清楚了,然后我們?cè)谀X補(bǔ)一下,值是這樣設(shè)置就去了,那么我獲取的時(shí)候是不是只要得到map也就是ThreadLocal.ThreadLocalMap threadLocals這個(gè)變量,再根據(jù)這個(gè)變量得到當(dāng)前線程的的數(shù)組,在通過數(shù)組的index是不是就可以得到當(dāng)前的對(duì)象,再根據(jù)的對(duì)象取出value,思路是沒有錯(cuò),我們看下源碼是不是這個(gè)樣子了

get:

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

是不是一模一樣了,其實(shí)它的源碼還是比較簡(jiǎn)單的,當(dāng)然還有更高級(jí)的用法下邊在上代碼:

 private ThreadLocal<Boolean> booleanThreadLocal = new ThreadLocal<Boolean>(){
        @Override
        protected Boolean initialValue() {
            return true;
        }
    };
 button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i(TAG, "onClick: booleanThreadLocal------->"+booleanThreadLocal.get());

            }
        });
11-24 08:02:02.332 26018-26018/com.example.administrator.dbhelp I/MainActivity: onClick: booleanThreadLocal------->true

重寫initialValue值返回true,這次我沒有設(shè)置值,確返回true,是不是和get方法有關(guān),當(dāng)我們我們?cè)O(shè)置值的時(shí)候當(dāng)前的線程的map為null,這時(shí) return setInitialValue();看下方法 :

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

是不是一樣的簡(jiǎn)單,邏輯很清楚,我們直接重寫了initialValue(),所以返回的value值就是我們重寫方法的返回值;到此源碼也就分析完畢了,這時(shí)我們?cè)倩剡^頭來看下Handler消息機(jī)制,Handler通過Looper.loop()方法輪詢消息,我們一般寫Handler的時(shí)候都是在主線程創(chuàng)建的,在程序啟動(dòng)的時(shí)候也就是加載ActivityThread類的時(shí)候系統(tǒng)已經(jīng)幫我們自動(dòng)的創(chuàng)建了主線程的Looper,通過上邊的圖也可知,可是如果我想在子線程創(chuàng)建Handler了,子線程是不是也需要自己的Looper;是不是自己也需要啟動(dòng)Looper.loop來循環(huán)消息,這時(shí)候ThreadLocal這個(gè)類就來了,ThreadLocal為當(dāng)前的每一個(gè)線程存儲(chǔ)一個(gè)Looper,每一個(gè)Looper也有唯一的一個(gè)消息隊(duì)列MessageQueue,所以在子線程new Handler()的時(shí)候需要我們手動(dòng)的去獲取當(dāng)前線程的Looper,主動(dòng)的調(diào)用Looper.prepare();

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

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

獲取當(dāng)前線程的Looper和MessageQueue

new Thread(new Runnable() {

                  private  Handler handler;

                  @Override
                  public void run() {
                          Looper.prepare();
                      handler = new Handler(){
                          @Override
                          public void handleMessage(Message msg) {
                                          Log.i(TAG, "handleMessage: "+Thread.currentThread().getName()+"workThread線程收到消息了-->");
                          }
                      };
         handler.sendEmptyMessage(0x10000);
                        Looper.loop();

                  }
              }).start();
----------------------------------------
或者來個(gè)暴力點(diǎn)的既然主線程已經(jīng)有了Looper了就用他已經(jīng)創(chuàng)建的好的
new Thread(new Runnable() {

                  private  Handler handler;

                  @Override
                  public void run() {
                      handler = new Handler(Looper.getMainLooper()){
                          @Override
                          public void handleMessage(Message msg) {
                        Log.i(TAG, "handleMessage: "+Thread.currentThread().getName()+"main線程收到消息了-->");
                          }
                      };
         handler.sendEmptyMessage(0x10000);

                  }
              }).start();

上邊兩個(gè)方法都可以使Handler 在子線程去處理,但是接受消息的結(jié)果當(dāng)然也是不一樣的,上邊的采用的子線程Looper,下邊是main線程Looper;

總結(jié):

其實(shí)在開發(fā)中用的ThreadLocal的地方極少,但是ThreadLocal也是不可忽視的一個(gè)重要點(diǎn),在面試的時(shí)候你能把ThreadLocal和Looper結(jié)合起來一起講,也許就是加分項(xiàng),再者ThreadLocal在某些特殊的場(chǎng)景的,通過它可以實(shí)現(xiàn)一個(gè)看起來比較復(fù)雜的功能,當(dāng)某些數(shù)據(jù)以線程為作用域并且不同線程要獲取不同數(shù)據(jù)的時(shí)候,就可以用到ThreadLocal;

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末打毛,一起剝皮案震驚了整個(gè)濱河市抵卫,隨后出現(xiàn)的幾起案子锋喜,更是在濱河造成了極大的恐慌履植,老刑警劉巖炬灭,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件指煎,死亡現(xiàn)場(chǎng)離奇詭異粗蔚,居然都是意外死亡尝偎,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門鹏控,熙熙樓的掌柜王于貴愁眉苦臉地迎上來致扯,“玉大人,你說我怎么就攤上這事当辐《督” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵瀑构,是天一觀的道長(zhǎng)裆针。 經(jīng)常有香客問我刨摩,道長(zhǎng),這世上最難降的妖魔是什么世吨? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任澡刹,我火速辦了婚禮,結(jié)果婚禮上耘婚,老公的妹妹穿的比我還像新娘罢浇。我一直安慰自己,他們只是感情好沐祷,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布嚷闭。 她就那樣靜靜地躺著,像睡著了一般赖临。 火紅的嫁衣襯著肌膚如雪胞锰。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天兢榨,我揣著相機(jī)與錄音嗅榕,去河邊找鬼。 笑死吵聪,一個(gè)胖子當(dāng)著我的面吹牛凌那,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播吟逝,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼帽蝶,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了块攒?” 一聲冷哼從身側(cè)響起励稳,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎局蚀,沒想到半個(gè)月后麦锯,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡琅绅,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年扶欣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片千扶。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡料祠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出澎羞,到底是詐尸還是另有隱情髓绽,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布妆绞,位于F島的核電站顺呕,受9級(jí)特大地震影響枫攀,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜株茶,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一蹦掐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧社裆,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽坞靶。三九已至,卻和暖如春簇抵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背典蜕。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓假瞬,卻偏偏與公主長(zhǎng)得像剪芥,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子益兄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

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