Handler往MessageQueue中添加數據,消息隊列是怎樣變化的?如何保證線程安全的?

多個Handler往MessageQueue中添加數據,其內部是如何保證線程安全的?

Handler是一個線程間通信的機制,很多消息都會從子線程發送至主線程,而主線程只有一個Looper,發送的消息都被放置在MessageQueue這個隊列中來,如何保證隊列的混亂(如何保證線程安全)?

看入隊列的方法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) {

          ......

        }
        return true;
    }

很清楚的看到這個方法裏面有鎖(synchronized),既然入隊列裏有鎖,那再看看取消息是不是也有鎖?

    Message next() {

        ......

            synchronized (this) {
               ......
            }

        ......
    }

是的,也是存在鎖的。

所以,它是通過synchronized來保證了線程的安全性。

 

Handler所發送的Delayed消息時間準確嗎?

實際上,這個問題與線程安全性爲同一個問題,多線程中線程一旦安全,時間就不能準確;時間一旦準確,線程就一定不安全。

所以,Handler所發送的Delayed消息時間基本準確,但不完全準確。

因爲多個線程去訪問這個隊列的時候,在放入對列和取出消息的時候都會加鎖,當第一個線程還沒有訪問完成的時候,第二個線程就無法使用,所以他實際的時間會被延遲。

 

我們在使用Message的時候應該怎樣創建它?

由於Message創建非常頻繁,如果不斷以new的方式去創建它,它的內存抖動是很恐怖的。

所以在Android的Message機制裏面,對Message的管理採用了享元設計模式

先來查看Message.obtain()都有哪些操作?

    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

obtain()維持了一個Message的pool(池子)。

    private static Message sPool;

我們在構建一個消息的時候,一般的步驟是先obtain一個消息,然後對它的各個字段進行設置,像target、data、when、flags...

當MessageQueuez去釋放消息的時候(quit),它只是把消息的內容置空了,然後再把這條處理的消息放到池子裏面來,讓池子不斷變大。

    /**
     * Recycles a Message that may be in-use.
     * Used internally by the MessageQueue and Looper when disposing of queued Messages.
     */
    void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

在這個池子裏面,最多放置50個消息。

    private static final int MAX_POOL_SIZE = 50;

如果消息超過了50個消息,這個池子也不要了,然後mMessage也爲空,則它也會被及時的回收。

    private void removeAllMessagesLocked() {
        Message p = mMessages;
        while (p != null) {
            Message n = p.next;
            p.recycleUnchecked();
            p = n;
        }
        mMessages = null;
    }

 

使用Handler的postDelay後消息隊列將會有怎樣的變化?

我們從postDelay的方法來開始追:

    public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

這時候就是給消息隊列添加一個消息時刻,如果這個消息隊列爲空,這個消息就不會被執行,只有一個消息,這個消息就不會被髮送,而是計算等待的時間。

在添加消息的時候,在MessageQueue的時候,他有一個計算,MessageQueue裏面有一個enqueueMessage()。在這個enqueueMessage中,一旦添加了消息之後,他就執行nativeWake()喚醒消息隊列,這個消息隊列就醒來。

            if (needWake) {
                nativeWake(mPtr);
            }

這個消息隊列醒來之後,在MessageQueue裏的next()函數就會觸發關於要等待多長時間的計算。

                	//開機到現在的毫秒數如果小於msg.when則代表還未到發送消息的時間
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        // 雖然有消息,但是還沒有到運行的時候
                        //計算還有等待多久,並賦值給nextPollTimeoutMillis
                    	//設置下一次查詢消息需要等待的時長
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    }

計算完這個等待時間之後,這個for循環結束,結束完之後回過頭來,就再跑回這裏再次睡眠。

            //native的方法,在沒有消息的時候回阻塞管道讀取端,只有nativePollOnce返回之後才能往下執行
            //阻塞操作,等待nextPollTimeoutMillis時長
            nativePollOnce(ptr, nextPollTimeoutMillis);

所以說,他會先計算需要等待的時間,計算完需要等待的時間之後,就會進行對應的操作,然後重新讓這個消息進行wait。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章