Android 線程簡單分析(一)
Android 并發(fā)之synchronized鎖住的是代碼還是對象(二)
Android 并發(fā)之CountDownLatch、CyclicBarrier的簡單應(yīng)用(三)
Android 并發(fā)HashMap和ConcurrentHashMap的簡單應(yīng)用(四)(待發(fā)布)
Android 并發(fā)之Lock析命、ReadWriteLock和Condition的簡單應(yīng)用(五)
Android 并發(fā)之CAS(原子操作)簡單介紹(六)
Android 并發(fā)Kotlin協(xié)程的重要性(七)(待發(fā)布)
Android 并發(fā)之AsyncTask原理分析(八)
Android 并發(fā)之Handler主卫、Looper、MessageQueue和ThreadLocal消息機(jī)制原理分析(九)
Android 并發(fā)之HandlerThread和IntentService原理分析(十)
Handler消息機(jī)制在Android中的重要性鹃愤,在Android中Handler消息機(jī)制其實(shí)也就涉及幾個(gè)組件:Handler簇搅、Looper、MesageQueue软吐、Mesage和ThreadLocal瘩将,下面來一個(gè)一個(gè)說說?
先拋出幾個(gè)異常凹耙?
- Handler消息機(jī)制的流程是什么姿现?
- Handler如何處理延遲消息?
- 為什么消息入隊(duì)列和消息出隊(duì)列會(huì)加同步代碼塊肖抱?會(huì)出現(xiàn)呢什么問題备典?
- 消息時(shí)如何進(jìn)入隊(duì)列的?
- 消息屏障也就是msg.target == null的消息的作用是什么或者如何處理的意述?
Handler消息機(jī)制的流程
Handler創(chuàng)建的時(shí)候會(huì)獲取當(dāng)前線程的Looper提佣,通過Looper獲取MessageQueue吮蛹,然后通過Handler發(fā)消息,最后Looper不斷輪訓(xùn)取出消息交給Handler處理拌屏。
public Handler(Callback callback, boolean async) {
//獲取當(dāng)前線程的Looper
mLooper = Looper.myLooper();
//如果獲取不到Looper則拋出異常匹涮,一般主線程中默認(rèn)系統(tǒng)已經(jīng)創(chuàng)建了Looper
if (mLooper == null) {
// 如果是在工作線程中,就需要手動(dòng)調(diào)用Looper.prepare()創(chuàng)建Looper
throw new RuntimeException( "Can't create handler inside thre" + Thread.currentThread()+ " that has not called Looper.prepare()");
}
//獲取到Looper的MessageQueue
mQueue = mLooper.mQueue;
mCallback = callback;
//是否是異步槐壳,后面會(huì)講
mAsynchronous = async;
}
源碼有注釋就不解析了,在看看sendMessageAtTime方法喜每。
// 不管是同步消息還是異步消息最終發(fā)消息都會(huì)調(diào)用此方法
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
//獲取消息隊(duì)列
MessageQueue queue = mQueue;
if (queue == null) {
// 如果沒有消息隊(duì)列务唐,則拋異常
RuntimeException e = new RuntimeException(this + " sendMessageAtTime() called with no mQueue");
return false;
}
// 消息準(zhǔn)備進(jìn)入隊(duì)列
return enqueueMessage(queue, msg, uptimeMillis);
}
在sendMessageAtTime方法最終調(diào)用enqueueMessage 方法并調(diào)用MessageQueue的enqueueMessage(msg, uptimeMillis)把消息放入隊(duì)列:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
來看看消息是如何加入MessageQuueue:
boolean enqueueMessage(Message msg, long when) {
//這里和next方法取出消息時(shí)判斷msg.target == null不矛盾,因?yàn)橄到y(tǒng)內(nèi)發(fā)消息并不經(jīng)過此方法
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带兜?
//因?yàn)橄㈥?duì)列內(nèi)部維護(hù)的是單鏈表枫笛,線程不安全,消息入隊(duì)列可能同時(shí)有消息出隊(duì)列
// 那么消息出隊(duì)列可能會(huì)把消息移除刚照,還有一種情況就是入隊(duì)時(shí)可能會(huì)造成順序錯(cuò)亂
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;
}
//標(biāo)志消息正在使用
msg.markInUse();
msg.when = when;
// 下面就是鏈表的操作了
Message p = mMessages; //head
boolean needWake; // 需不需要阻塞
//如果消息為null刑巧、時(shí)間為0或者時(shí)間比隊(duì)頭的小,直接插入在對頭无畔,如果隊(duì)列正在阻塞就喚醒
if (p == null || when == 0 || when < p.when) { //消息入隊(duì)列啊楚,按照時(shí)間從小到大排序
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// 只有在Delay的消息 mBlocked = true并且是異步的,異步消息優(yōu)先級高的很
//插入隊(duì)列的中間浑彰。 通常我們不必喚醒事件隊(duì)列恭理,除非隊(duì)列頭部有屏障,并且消息是隊(duì)列中最早的異步消息郭变。
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;
prev.next = msg;
}
/**
* 在next中颜价,如果有阻塞(沒有消息了或者只有Delay的消息),會(huì)把mBlocked這個(gè)變量標(biāo)記為true诉濒,
* 在下一個(gè)Message進(jìn)隊(duì)時(shí)會(huì)判斷這個(gè)message的位置周伦,如果在隊(duì)首就會(huì)調(diào)用nativeWake()方法喚醒線程!
*/
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
在來看看消息入隊(duì)列:
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; /
int nextPollTimeoutMillis = 0;
// 不斷循環(huán)取出消息
for (; ; ) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
//掛起未荒,即阻塞
nativePollOnce(ptr, nextPollTimeoutMillis);
// 為什么加synchronized专挪?
//因?yàn)橄㈥?duì)列內(nèi)部維護(hù)的是單鏈表,線程不安全片排,消息入隊(duì)列可能同時(shí)有消息出隊(duì)列
// 那么消息出隊(duì)列可能會(huì)把消息移除狈蚤,還有一種情況就是入隊(duì)時(shí)可能會(huì)造成順序錯(cuò)亂
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 同步屏障點(diǎn),優(yōu)先處理所有的異步消息
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
/**
*
* 1划纽、每個(gè)入MessageQueue之前是都有出發(fā)時(shí)間when脆侮,是按照出發(fā)使勁按從小到大排序的,默認(rèn)觸發(fā)時(shí)間為當(dāng)前系統(tǒng)時(shí)間勇劣;
* 2靖避、延遲發(fā)送的話:系統(tǒng)時(shí)間 + 延遲時(shí)間潭枣;
* 3、MessageQueue輪詢時(shí)會(huì)判斷:如果 觸發(fā)時(shí)間 > 系統(tǒng)時(shí)間 說明時(shí)延遲消息幻捏,暫時(shí)不處理并阻塞對應(yīng)的觸發(fā)時(shí)間減去系統(tǒng)時(shí)間
*/
if (now < msg.when) {// 觸發(fā)時(shí)間 > 系統(tǒng)時(shí)間 延遲消息
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else { // 正常消息
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
/**
*
* 1.如果nextPollTimeoutMillis=-1盆犁,一直阻塞不會(huì)超時(shí)。
*
* 2.如果nextPollTimeoutMillis=0篡九,不會(huì)阻塞谐岁,立即返回。
*
* 3.如果nextPollTimeoutMillis>0榛臼,最長阻塞nextPollTimeoutMillis毫秒(超時(shí))伊佃,如果期間有程序喚醒會(huì)立即返回。
*/
nextPollTimeoutMillis = -1;
}
if (mQuitting) {
dispose();
return null;
}
/**
* 如果有阻塞(沒有消息了或者只有Delay的消息)沛善,會(huì)把mBlocked這個(gè)變量標(biāo)記為true航揉,
* 在下一個(gè)Message進(jìn)隊(duì)時(shí)會(huì)判斷這個(gè)message的位置,如果在隊(duì)首就會(huì)調(diào)用nativeWake()方法喚醒線程金刁!
* 也就是在enqueueMessage方法中調(diào)用nativeWake()方法喚醒線程帅涂!
* 注意:nativePollOnce 方法有兩種喚醒方式:1、超時(shí)尤蛮;2媳友、nativeWake方法*/
if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
nextPollTimeoutMillis = 0;
}
}
注釋已經(jīng)很清楚了
前面講到Handler創(chuàng)建時(shí)會(huì)取出當(dāng)前線程的Looper,并獲取Looper中的MessageQueue产捞,然后發(fā)消息到MessageQueue庆锦,通過Looper不斷輪訓(xùn)取出消息交給Handler處理,看看Looper是如何創(chuàng)建轧葛?
//構(gòu)造方法
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
// Looper 創(chuàng)建
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));
}
Looper的構(gòu)造方法是private的搂抒,Looper通過 Looper.prepare()方法創(chuàng)建,Looper創(chuàng)建成功并且將Looper實(shí)例保存在ThreadLocal中尿扯,在Looper創(chuàng)建時(shí)求晶,會(huì)創(chuàng)建一個(gè)MessageQueue消息隊(duì)列衷笋。
調(diào)用Looper.loop()方法開始輪訓(xùn)同步取出消息交給Handler處理芳杏;
loop
public static void loop() {
//獲取當(dāng)前線程綁定的Looper,如果為null則拋出異常辟宗,否則取出MessageQueue
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// 開始不斷輪訓(xùn)取出消息
for (; ; ) {
Message msg = queue.next(); // MessageQueue 可能會(huì)被掛起或阻塞爵赵,底層管道發(fā)通知
// 如果消息不為null,調(diào)用dispatchMessage分發(fā)消息
if (msg == null) { return; }
msg.target.dispatchMessage(msg);
//把消息放入消息池中
msg.recycleUnchecked();
}
}
loop
public static @Nullable Looper myLooper() {
return sThreadLocal.get(); //獲取與當(dāng)前線程綁定的Looper
}
Handler中的dispatchMessage方法對消息進(jìn)行分發(fā)
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
Message中的recycleUnchecked方法:
void recycleUnchecked() {
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++;
}
}
}
ThreadLocal
Looper是存放在ThreadLocal中泊脐,ThreadLocal的實(shí)例代表了一個(gè)線程局部的變量空幻,每條線程都只能看到自己的值,并不會(huì)意識(shí)到其它的線程中也存在該變量容客。它采用采用空間來換取時(shí)間的方式秕铛,解決多線程中相同變量的訪問沖突問題约郁,所以Looper使用ThreadLocal保存在當(dāng)前線程中,其他線程對它不可見但两,這樣就解決了鬓梅,每個(gè)線程只有一個(gè)Looper實(shí)例。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocal作為Looper的靜態(tài)成員變量谨湘,即Looper全局僅僅只有一個(gè)ThreadLocal绽快,而存放Looper對象的并不是TheradLocal,而是ThreadLocalMap紧阔,ThreadLocalMap是ThreadLocal的內(nèi)部類是一個(gè)自定義的HashMap坊罢,而ThreadLocalMap又作為Thread類的成員成員變量即每一個(gè)Thread的實(shí)例僅有一個(gè)ThreadLocalMap,所以總結(jié)就是ThreadLocal僅僅是使用了裝飾模式封裝了ThreadLocalMap并對ThreadLocalMap的功能進(jìn)行了增強(qiáng)寓辱,中一個(gè)過程就是,Looper調(diào)用ThreadLocal赤拒,然后ThreadLocal通過Thread得到當(dāng)前線程的ThreadLocalMap秫筏,并將Looper作為value存儲(chǔ)到ThreadLocalMap中而Key是ThreadLocal。
每個(gè)Thread的對象都有一個(gè)ThreadLocalMap挎挖,而ThreadLocalMap是ThreadLocal的靜態(tài)內(nèi)部類这敬,當(dāng)創(chuàng)建一個(gè)ThreadLocal的時(shí)候,就會(huì)將該ThreadLocal對象作為Key添加到該Map中蕉朵,其中Key就是ThreadLocal崔涂,值可以是Looper。
在該類中始衅,我覺得最重要的方法就是兩個(gè):set()和get()方法冷蚂。當(dāng)調(diào)用ThreadLocal的get()方法的時(shí)候,會(huì)先找到當(dāng)前線程的ThreadLocalMap汛闸,然后再找到對應(yīng)的值蝙茶。set()方法也是一樣。
下面貼出一張Looper保存到TheadLocal中的時(shí)序圖:
回答問題:
Handler消息機(jī)制的流程是什么诸老?(文章首部有圖)
1隆夯、Handler創(chuàng)建的時(shí)候會(huì)獲取當(dāng)前線程的Looper;
2别伏、通過Handler通過Looper獲取MessageQueuer發(fā)消息蹄衷;
3、Looper不斷輪訓(xùn)取出消息交給Handler處理厘肮。Handler如何處理延遲消息愧口?
1、每個(gè)消息都有觸發(fā)時(shí)間when类茂,消息進(jìn)入隊(duì)列是按照觸發(fā)時(shí)間按從小到大排序的调卑;
2抡砂、默認(rèn)觸發(fā)時(shí)間為當(dāng)前系統(tǒng)時(shí)間,延遲發(fā)送的:系統(tǒng)時(shí)間 + 延遲時(shí)間恬涧;
3注益、MessageQueue輪詢時(shí)會(huì)判斷:如果觸發(fā)時(shí)間 > 系統(tǒng)時(shí)間 說明時(shí)延遲消息暫時(shí)不處理并阻塞對應(yīng)的觸發(fā)時(shí)間減去系統(tǒng)時(shí)間;
4溯捆、Handler發(fā)消息進(jìn)入隊(duì)列時(shí)是根據(jù)時(shí)間wahen的消息排序的丑搔,在取消息時(shí)根據(jù)當(dāng)前系統(tǒng)時(shí)間對比,如果系統(tǒng)時(shí)間now < when,說明是延遲消息提揍,
那么 when - now = nextPollTimeoutMillis啤月,調(diào)用nativePollOnce(ptr, nextPollTimeoutMillis)進(jìn)行掛起(底層的技術(shù)),
注意:nativePollOnce有超時(shí)換新劳跃,當(dāng)然在消息入隊(duì)列也會(huì)判斷是否喚醒nativeWake(mPtr)為什么消息入隊(duì)列和消息出隊(duì)列會(huì)加同步代碼塊谎仲?會(huì)出現(xiàn)呢什么問題?
1刨仑、因?yàn)橄㈥?duì)列內(nèi)部維護(hù)的是單鏈表郑诺,是線程不安全的,
2杉武、消息入隊(duì)列可能同時(shí)有消息出隊(duì)列 那么消息出隊(duì)列可能會(huì)把消息移除辙诞,還有一種情況就是入隊(duì)時(shí)可能會(huì)造成順序錯(cuò)亂。為什么兩個(gè)鎖在兩個(gè)不同方法的同步代碼塊能起到同步轻抱?
1飞涂、實(shí)際上是同一個(gè)對象,即MessageQueue祈搜,所以他們是同一個(gè)監(jiān)視器较店。
如果想更進(jìn)一步了解synchronized請移步Android 并發(fā)之synchronized鎖住的是代碼還是對象(二)介紹消息時(shí)如何進(jìn)入隊(duì)列的?
1容燕、消息隊(duì)列內(nèi)部維護(hù)一個(gè)單鏈表泽西,消息進(jìn)入隊(duì)列時(shí)是根據(jù)時(shí)間wahen的消息排序的,從小到大排序消息屏障也就是msg.target == null的消息的作用是什么或者如何處理的缰趋?
1捧杉、消息屏障是為了區(qū)別同步消息和異步消息的優(yōu)先級;
2秘血、在Handler中異步消息優(yōu)先處理味抖,而我們平時(shí)發(fā)送的消息全是同步消息,一般異步消息系統(tǒng)發(fā)的灰粮,如:系統(tǒng)發(fā)送更新UI的消息仔涩,這樣能夠在第一時(shí)間更新UI。-
為什么在子線程中創(chuàng)建Handler會(huì)crach粘舟,在主線程中就不會(huì)發(fā)生呢熔脂?
1佩研、這個(gè)問題如果要展開來講,就是一篇文章霞揉,為了回答這個(gè)問題旬薯,我先上ActivityThread部分代碼:public static void main(String[] args) { // ...此處省略一萬行代碼 // 準(zhǔn)備主線程的 Looper Looper.prepareMainLooper(); // 創(chuàng)建 ActivityThread實(shí)例,用于管理應(yīng)用程序進(jìn)程中主線程的執(zhí)行 ActivityThread thread = new ActivityThread(); // 進(jìn)入 attach 方法 thread.attach(false, startSeq)适秩; // ...此處省略一萬行代碼 // 開始 Looper Looper.loop();
}
大家都知道App啟動(dòng)一般都是先進(jìn)入ActivityThread的 main 方法绊序,看到Handler的身影。在安卓中都是以消息進(jìn)行驅(qū)動(dòng)秽荞,在這里也不例外骤公,我們可以看到先進(jìn)行 Looper 的準(zhǔn)備,在最后開啟 Looper 進(jìn)行輪訓(xùn)獲取消息扬跋,用于處理傳到主線程的消息阶捆。這也是為什么我們在主線程不需要先進(jìn)行 Looper 的準(zhǔn)備和開啟而子線程則必須要進(jìn)行 Looper 的準(zhǔn)備和開啟。