多個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。