生產(chǎn)者和消費(fèi)者模式在Android中的應(yīng)用

What

所謂生產(chǎn)者消費(fèi)者模式馏艾,就是一個(gè)地方無(wú)腦生產(chǎn)他宛,一個(gè)地方無(wú)腦消費(fèi)四濒,通過(guò)一個(gè)中間緩沖區(qū)建立的一種模式换况。這樣的解耦是不是很多人所向往的职辨,而解耦的關(guān)鍵是如何使用中間的緩沖區(qū)。生活中的例子也有很多戈二,像賣(mài)手機(jī)的舒裤,他們只負(fù)責(zé)生產(chǎn),而我們只負(fù)責(zé)消費(fèi)觉吭,中間的緩沖區(qū)便是他們的庫(kù)存腾供。再比如郵局,我們只負(fù)責(zé)寫(xiě)信鲜滩,收信人只負(fù)責(zé)收信伴鳖,中間的緩沖區(qū)便是郵局。還有徙硅,坐地鐵榜聂,上班打卡。嗓蘑。须肆。生活中處處充滿著這個(gè)模型。

生產(chǎn)者和消費(fèi)者模型

有了生產(chǎn)和消費(fèi)桩皿,但是世界永遠(yuǎn)唯一不變的是變化豌汇,于是就產(chǎn)生了各種問(wèn)題,生產(chǎn)者和消費(fèi)者的量不一致泄隔,時(shí)間的把控瘤礁,效率的高低,都是問(wèn)題出現(xiàn)的因素梅尤。在美麗的大Android中很多地方也運(yùn)用到了這個(gè)模型,同樣的岩调,也會(huì)出現(xiàn)這個(gè)問(wèn)題巷燥,那么Android中是如何處理這些問(wèn)題的呢?他的緩沖區(qū)是如何做的呢号枕?

How

首先缰揪,看看Android中常用到這個(gè)模型的有哪些應(yīng)用?

曾經(jīng)面試的問(wèn)題葱淳,Android中有幾種方式可以在子線程中更新UI钝腺?
初學(xué)者看到這里,應(yīng)該會(huì)自豪的說(shuō):

1赞厕,runOnUiThread
2艳狐,view.post()
3,handler
runOnUiThread
view.post()

前兩種方式的源碼 其內(nèi)部都實(shí)現(xiàn)了mHandler.post(action)方法皿桑,說(shuō)明這三種方式其實(shí)毫目,就是一種方式蔬啡,通過(guò)Handler機(jī)制實(shí)現(xiàn),關(guān)于Handler機(jī)制實(shí)現(xiàn)镀虐,請(qǐng)聽(tīng)下回分解箱蟆。

另外還有最熟悉的Toast

Toast內(nèi)部源碼

其內(nèi)部也是Handler:mHandler.obtainMessage(0, windowToken).sendToTarget();

Why

內(nèi)部的實(shí)現(xiàn)都是Hander機(jī)制,其實(shí)Android消息機(jī)制的核心便是Handler機(jī)制刮便,而實(shí)現(xiàn)消息機(jī)制模型就是生產(chǎn)者消費(fèi)者模型空猜。那么,Handler機(jī)制是如何實(shí)現(xiàn)的呢恨旱?

查看源碼一路追蹤辈毯,撥開(kāi)層層迷霧,可以在MessageQueue,Message中查看得到生產(chǎn)者消費(fèi)者模型的影子,Message就是生產(chǎn)出來(lái)的事物窖杀,而MessageQueue實(shí)現(xiàn)了生產(chǎn)和消費(fèi)操作功能漓摩。

MeesageQueue,具體查看代碼如下:
enqueueMessage()

boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    if (msg.isInUse()) {
        throw new IllegalStateException(msg + " This message is already in use.");
    }

    synchronized (this) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            Log.w(TAG, e.getMessage(), e);
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

next()

 Message next() {
    // Return here if the message loop has already quit and been disposed.
    // This can happen if the application tries to restart a looper after quit
    // which is not supported.
    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }

            // Process the quit message now that all pending messages have been handled.
            if (mQuitting) {
                dispose();
                return null;
            }

            // If first time idle, then get the number of idlers to run.
            // Idle handles only run if the queue is empty or if the first message
            // in the queue (possibly a barrier) is due to be handled in the future.
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }

            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // Run the idle handlers.
        // We only ever reach this code block during the first iteration.
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler

            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        // Reset the idle handler count to 0 so we do not run them again.
        pendingIdleHandlerCount = 0;

        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }

分析如下:

生產(chǎn)物

Message鏈表.png

生產(chǎn)者:

 enqueueMessage()  生產(chǎn)的對(duì)象為Message
          
       if(beforeMessag==null||when=0||when<beforeMessag.when){
                    initMessage;
       }else{//新消息,是入隊(duì)操作
                prevMsg.next=curMsg;
       }

        Message p=Message mMessage;
        Message prev;
        loop  //循環(huán)取出當(dāng)前鏈表最后一個(gè)message,賦值給prev;
          ->prev =p;
          ->p=p.next;
        //賦值給Next
        msg.next=p=null;
        prev.next =msg;

消費(fèi)者:

next()
    loop
       ->Message prevMsg=null; Message msg=mMessages;
             //將下一個(gè)Msg上移入客,for loop 將剩下來(lái)的msg一一往前移動(dòng)
       ->   if(prevMsg!=null) prevMsg.next=msg.next;
       ->     else mMessages=msg.next;//   主鏈表上移一個(gè)msg
       ->    return msg;

1管毙,enqueueMessage() 為生產(chǎn)線程執(zhí)行,入隊(duì)一個(gè)Message ,return true桌硫。

2夭咬,next() 為消費(fèi)線程執(zhí)行,出隊(duì):在Looper.loop()中不斷取, 而在next()中也是loop 只要取到了便return msg 否則wait铆隘。next加了一個(gè)同步鎖,保證了與enqueue的互斥卓舵。enqueue 同樣也添加了同步鎖,從而保證了與next的互斥:將message添加到Message鏈表中去,判斷,如果出現(xiàn)阻塞了,需要進(jìn)行喚醒操作膀钠。妥妥的生產(chǎn)者消費(fèi)者模型掏湾。

總結(jié)

生產(chǎn)者和消費(fèi)者的精髓是:

不同線程操作同一對(duì)象的不同方法,但是要保持其互斥,也不能出現(xiàn)死鎖的情況,條件滿足就通知其他等待的線程 ,條件不滿足,就休眠等待。

在Thread-1的生產(chǎn)者只負(fù)責(zé)生產(chǎn),在Thread-2的消費(fèi)者則只負(fù)責(zé)消費(fèi),操作互斥,當(dāng)生產(chǎn)者達(dá)到上限則進(jìn)行等待,反之消費(fèi)者達(dá)到上限所有線程就等待肿嘲。

【引用】
1融击,模式解釋靈感:戳這里看大神的解釋
2,MessageQueue源碼解析
3雳窟,Toast源碼解析尊浪,艾瑪,和我看的順序一樣一樣的

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末封救,一起剝皮案震驚了整個(gè)濱河市拇涤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌誉结,老刑警劉巖鹅士,帶你破解...
    沈念sama閱讀 221,273評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異惩坑,居然都是意外死亡如绸,警方通過(guò)查閱死者的電腦和手機(jī)嘱朽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)怔接,“玉大人搪泳,你說(shuō)我怎么就攤上這事《笃辏” “怎么了岸军?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,709評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)瓦侮。 經(jīng)常有香客問(wèn)我艰赞,道長(zhǎng),這世上最難降的妖魔是什么肚吏? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,520評(píng)論 1 296
  • 正文 為了忘掉前任方妖,我火速辦了婚禮,結(jié)果婚禮上罚攀,老公的妹妹穿的比我還像新娘党觅。我一直安慰自己,他們只是感情好斋泄,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布杯瞻。 她就那樣靜靜地躺著,像睡著了一般炫掐。 火紅的嫁衣襯著肌膚如雪魁莉。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,158評(píng)論 1 308
  • 那天募胃,我揣著相機(jī)與錄音旗唁,去河邊找鬼。 笑死痹束,一個(gè)胖子當(dāng)著我的面吹牛检疫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播参袱,決...
    沈念sama閱讀 40,755評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼秽梅!你這毒婦竟也來(lái)了抹蚀?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,660評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤企垦,失蹤者是張志新(化名)和其女友劉穎环壤,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體钞诡,經(jīng)...
    沈念sama閱讀 46,203評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡郑现,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評(píng)論 3 340
  • 正文 我和宋清朗相戀三年湃崩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片接箫。...
    茶點(diǎn)故事閱讀 40,427評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡攒读,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辛友,到底是詐尸還是另有隱情薄扁,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評(píng)論 5 349
  • 正文 年R本政府宣布废累,位于F島的核電站邓梅,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏邑滨。R本人自食惡果不足惜日缨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望掖看。 院中可真熱鬧匣距,春花似錦、人聲如沸乙各。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,272評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)耳峦。三九已至恩静,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蹲坷,已是汗流浹背驶乾。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,393評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留循签,地道東北人级乐。 一個(gè)月前我還...
    沈念sama閱讀 48,808評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像县匠,于是被迫代替她去往敵國(guó)和親风科。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評(píng)論 2 359

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