你知道Thread執行緒是如何運作的嗎?
背景介紹
我們在Android開發過程中,幾乎都離不開執行緒。但是你對執行緒的瞭解有多少呢?它完美執行的背後,究竟隱藏了多少不為人知的祕密呢?執行緒間互通暗語,傳遞資訊究竟是如何做到的呢?Looper、Handler、MessageQueue究竟在這背後進行了怎樣的運作。本期,讓我們一起從Thread開始,逐步探尋這個完美的執行緒鏈背後的祕密。
注意,大部分分析在程式碼中,所以請仔細關注程式碼哦!
從Thread的建立流程開始
在這一個環節,我們將一起一步步的分析Thread的建立流程。
話不多說,直接程式碼裡看。
執行緒建立的起始點init()
// 建立Thread的公有建構函式,都呼叫的都是這個私有的init()方法。我們看看到底幹什麼了。
/**
*
* @param 執行緒組
* @param 就是我們平時接觸最多的Runnable同學
* @param 指定執行緒的名稱
* @param 指定執行緒堆疊的大小
*/
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
Thread parent = currentThread();
//先獲取當前執行中的執行緒。這一個Native函式,暫時不用理會它怎麼做到的。黑盒思想,哈哈!
if (g == null) {
g = parent.getThreadGroup();
//如果沒有指定ThreadGroup,將獲取父執行緒的TreadGroup
}
g.addUnstarted();
//將ThreadGroup中的就緒執行緒計數器增加一。注意,此時執行緒還並沒有被真正加入到ThreadGroup中。
this.group = g;
//將Thread例項的group賦值。從這裡開始執行緒就擁有ThreadGroup了。
this.target = target;
//給Thread例項設定Runnable。以後start()的時候執行的就是它了。
this.priority = parent.getPriority();
//設定執行緒的優先權重為父執行緒的權重
this.daemon = parent.isDaemon();
//根據父執行緒是否是守護執行緒來確定Thread例項是否是守護執行緒。
setName(name);
//設定執行緒的名稱
init2(parent);
//納尼?又一個初始化,引數還是父執行緒。不急,稍後在看。
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
//設定執行緒的堆疊大小
tid = nextThreadID();
//執行緒的id。這是個靜態變數,呼叫這個方法會自增,然後作為執行緒的id。
}
第二個init2()
private void init2(Thread parent) {
this.contextClassLoader = parent.getContextClassLoader();
//設定ClassLoader成員變數
this.inheritedAccessControlContext = AccessController.getContext();
//設定訪問許可權控制環境
if (parent.inheritableThreadLocals != null) {
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(
//建立Thread例項的ThreadLoacaleMap。需要用到父執行緒的ThreadLocaleMap,目的是為了將父執行緒中的變數副本拷貝一份到當前執行緒中。
//ThreadLocaleMap是一個Entry型的陣列,Thread例項會將變數副本儲存在這裡面。
parent.inheritableThreadLocals);
}
}```
至此,我們的Thread就初始化完成了,Thread的幾個重要成員變數都賦值了。
啟動執行緒,開車啦!
通常,我們這樣了啟動一條執行緒。
Thread threadDemo = new Thread(() -> {
});
threadDemo.start();`
那麼start()背後究竟隱藏著什麼樣不可告人的祕密呢?是人性的扭曲?還是道德的淪喪?讓我們一起點進start()。探尋start()背後的祕密。
//如我們所見,這個方法是加了鎖的。
//原因是避免開發者在其它執行緒呼叫同一個Thread例項的這個方法,從而儘量避免丟擲異常。
//這個方法之所以能夠執行我們傳入的Runnable裡的run()方法,
//是應為JVM呼叫了Thread例項的run()方法。
public synchronized void start() {
//檢查執行緒狀態是否為0,為0表示是一個新狀態,即還沒被start()過。不為0就丟擲異常。
//就是說,我們一個Thread例項,我們只能呼叫一次start()方法。
if (threadStatus != 0)
throw new IllegalThreadStateException();
//從這裡開始才真正的執行緒加入到ThreadGroup組裡。
//再重複一次,前面只是把nUnstartedThreads這個計數器進行了增量,並沒有新增執行緒。
//同時,當執行緒啟動了之後,nUnstartedThreads計數器會-1。因為就緒狀態的執行緒少了一條啊!
group.add(this);
started = false;
try {
nativeCreate(this, stackSize, daemon);
//又是個Native方法。這裡交由JVM處理,會呼叫Thread例項的run()方法。
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
//如果沒有被啟動成功,Thread將會被移除ThreadGroup,
//同時,nUnstartedThreads計數器又增量1了。
}
} catch (Throwable ignore) {
}
}
}```
好把,最精華的函式是native的,先當黑盒處理吧。只要知道它能夠呼叫到Thread例項的run()方法就行了。那我們再看看run()方法到底幹了什麼神奇的事呢?
//沒錯,就是這麼簡單!僅僅呼叫了Runnable型別的成員變數target的run()方法。
//至此,我們需要執行的程式碼就執行起來了。
//至於這個@Overrid的存在,完全是因為Thread本身也是一個Runnable!
//就是說,我們的Thread也可以作為一個Runnable來使用。
@Override
public void run() {
if (target != null) {
target.run();
}
}```
黑實驗
public void test_1() {
Thread thread1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName());
}, "Thread_1");
Thread thread2 = new Thread(thread1, "Thread_2");
thread2.start();
}
---
輸出:
Thread_2```
上面的實驗表明了,我們完全可以用Thread來作為Runnable。
幾個常見的執行緒手段(操作)
Thread.sleep()那不可告人的祕密
我們平時使用Thread.sleep()的頻率也比較高,所以我們在一起研究研究Thread.sleep()被呼叫的時候發生了什麼。
在開始之前,先介紹一個概念——納秒。1納秒=十億分之一秒。可見用它計時將會非常的精準。但是由於裝置限制,這個值有時候並不是那麼準確,但還是比毫秒的控制粒度小很多。
//平時我們呼叫的Thread.sleep(long)最後呼叫到這個方法來,後一個陌生一點的引數就是納秒。
//你可以在納秒級控制執行緒。
public static void sleep(long millis, int nanos)
throws InterruptedException {
//下面三個檢測毫秒和納秒的設定是否合法。
if (millis < 0) {
throw new IllegalArgumentException("millis < 0: " + millis);
}
if (nanos < 0) {
throw new IllegalArgumentException("nanos < 0: " + nanos);
}
if (nanos > 999999) {
throw new IllegalArgumentException("nanos > 999999: " + nanos);
}
if (millis == 0 && nanos == 0) {
if (Thread.interrupted()) {
//當睡眠時間為0時,檢測執行緒是否中斷,
//並清除執行緒的中斷狀態標記。這是個Native的方法。
throw new InterruptedException();
//如果執行緒被設定了中斷狀態為true了(呼叫Thread.interrupt())。
//那麼他將丟擲異常。如果在catch住這個異常之後return執行緒,那麼執行緒就停止了。
//需要注意,在呼叫了Thread.sleep()之後,再呼叫isInterrupted()得到的結果永遠是False。
//別忘了Thread.interrupted()在檢測的同時還會清除標記位置哦!
}
return;
}
long start = System.nanoTime();
//類似System.currentTimeMillis()。但是獲取的是納秒,可能不準。
long duration = (millis * NANOS_PER_MILLI) + nanos;
Object lock = currentThread().lock;
//獲得當前執行緒的鎖。
synchronized (lock) {
//對當前執行緒的鎖物件進行同步操作
while (true) {
sleep(lock, millis, nanos);
//這裡又是一個Native的方法,並且也會丟擲InterruptedException異常。
//據我估計,呼叫這個函式睡眠的時長是不確定的。
long now = System.nanoTime();
long elapsed = now - start;
//計算執行緒睡了多久了
if (elapsed >= duration) {
//如果當前睡眠時長,已經滿足我們的需求,就退出迴圈,睡眠結束。
break;
}
duration -= elapsed;
//減去已經睡眠的時間,重新計算需要睡眠的時長。
start = now;
millis = duration / NANOS_PER_MILLI;
//重新計算毫秒部分
nanos = (int) (duration % NANOS_PER_MILLI);
//重新計算微秒部分
}
}
}```
通過上面的分析可以知道,使執行緒休眠的核心方法就是一個Native函式sleep(lock, millis, nanos),並且它休眠的時常是不確定的。因此,Thread.sleep()方法使用了一個迴圈,每次檢查休眠時長是否滿足需求。
同時,需要注意一點,如果執行緒的interruted狀態在呼叫sleep()方法時被設定為true,那麼在開始休眠迴圈前會丟擲InterruptedException異常。
Thread.yield()究竟隱藏了什麼?
這個方法是Native的。呼叫這個方法可以提示cpu,當前執行緒將放棄目前cpu的使用權,和其它執行緒重新一起爭奪新的cpu使用許可權。當前執行緒可能再次獲得執行,也可能沒獲得。就醬。
無處不在的wait()究竟是什麼?
大家一定經常見到,不論是哪一個物件的例項,都會在最下面出現幾個名為wait()的方法。等待?它們究竟是怎樣的一種存在,讓我們一起點選去看看。
哎喲我去,都是Native函式啊。
image
那就看看文件它到底是什麼吧。
根據文件的描述,wait()配合notify()和notifyAll()能夠實現執行緒間通訊,即同步。線上程中呼叫wait()必須在同步程式碼塊中呼叫,否則會丟擲IllegalMonitorStateException異常。因為wait()函式需要釋放相應物件的鎖。當執行緒執行到wait()時,物件會把當前執行緒放入自己的執行緒池中,並且釋放鎖,然後阻塞在這個地方。直到該物件呼叫了notify()或者notifyAll()後,該執行緒才能重新獲得,或者有可能獲得物件的鎖,然後繼續執行後面的語句。
呃。。。好吧,在說明一下notify()和notifyAll()的區別。
notify()
呼叫notify()後,物件會從自己的執行緒池中(也就是對該物件呼叫了wait()函式的執行緒)隨機挑選一條執行緒去喚醒它。也就是一次只能喚醒一條執行緒。如果在多執行緒情況下,只呼叫一次notify(),那麼只有一條執行緒能被喚醒,其它執行緒會一直在
notifyAll()
呼叫notifyAll()後,物件會喚醒自己的執行緒池中的所有執行緒,然後這些執行緒就會一起搶奪物件的鎖。
扒一扒Looper、Handler、MessageQueue之間的愛恨情仇
我們可能過去都寫過形如這樣的程式碼:
new Thread(()->{
...
Looper.prepare();
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Looper.loop();
}).start()```
很多同學知道,線上程中使用Handler時(除了Android主執行緒)必須把它放在Looper.prepare()和Looper.loop()之間。否則會丟擲RuntimeException異常。但是為什麼要這麼做呢?下面我們一起來扒一扒這其中的內幕。
image
從Looper.prepare()開始
當Looper.prepare()被呼叫時,發生了什麼?
public static void prepare() {
prepare(true);
//最終其實執行的是私有方法prepare(boolean quitAllowed)中的邏輯
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
//先嚐試獲取是否已經存在一個Looper在當前執行緒中,如果有就拋個異常。
//這就是為什麼我們不能在一個Thread中呼叫兩次Looper.prepare()的原因。
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
//首次呼叫的話,就建立一個新的Looper。
}
//Looper的私有建構函式
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
//建立新的MessageQueue,稍後在來扒它。
mThread = Thread.currentThread();
//把當前的執行緒賦值給mThread。
}```
經過上面的分析,我們已經知道Looper.prepare()呼叫之後發生了什麼。
但是問題來了!sThreadLocal是個靜態的ThreadLocal 例項(在Android中ThreadLocal的範型固定為Looper)。就是說,當前程式中的所有執行緒都共享這一個ThreadLocal。那麼,Looper.prepare()既然是個靜態方法,Looper是如何確定現在應該和哪一個執行緒建立繫結關係的呢?我們接著往裡扒。
來看看ThreadLocal的get()、set()方法。
public T get() {
Thread t = Thread.currentThread();
//重點啊!獲取到了當前執行的執行緒。
ThreadLocalMap map = getMap(t);
//取出當前執行緒的ThreadLocalMap。這個東西是個重點,前面已經提到過。
//忘了的同學在前面再看看。
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
//可以看出,每條執行緒的ThreadLocalMap中都有一個<ThreadLocal,Looper>鍵值對。
//繫結關係就是通過這個鍵值對建立的。
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
public void set(T value) {
Thread t = Thread.currentThread();
//同樣先獲取到當前的執行緒
ThreadLocalMap map = getMap(t);
//獲取執行緒的ThreadLocalMap
if (map != null)
map.set(this, value);
//儲存鍵值對
else
createMap(t, value);
}```
建立Handler
Handler可以用來實現執行緒間的通行。在Android中我們在子執行緒作完資料處理工作時,就常常需要通過Handler來通知主執行緒更新UI。平時我們都使用new Handler()來在一個執行緒中建立Handler例項,但是它是如何知道自己應該處理那個執行緒的任務呢。下面就一起扒一扒Handler。
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) { //可以看到,最終呼叫了這個方法。
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
//重點啊!在這裡Handler和當前Thread的Looper繫結了。
//Looper.myLooper()就是從ThreadLocale中取出當前執行緒的Looper。
if (mLooper == null) {
//如果子執行緒中new Handler()之前沒有呼叫Looper.prepare(),那麼當前執行緒的Looper就還沒建立。
//就會丟擲這個異常。
throw new RuntimeException(
"Can`t create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
//賦值Looper的MessageQueue給Handler。
mCallback = callback;
mAsynchronous = async;
}
Looper.loop()
我們都知道,在Handler建立之後,還需要呼叫一下Looper.loop(),不然傳送訊息到Handler沒有用!接下來,扒一扒Looper究竟有什麼樣的魔力,能夠把訊息準確的送到Handler中處理。
public static void loop() {
final Looper me = myLooper();
//這個方法前面已經提到過了,就是獲取到當前執行緒中的Looper物件。
if (me == null) {
//沒有Looper.prepare()是要報錯的!
throw new RuntimeException("No Looper; Looper.prepare() wasn`t called on this thread.");
}
final MessageQueue queue = me.mQueue;
//獲取到Looper的MessageQueue成員變數,這是在Looper建立的時候new的。
//這是個Native方法,作用就是檢測一下當前執行緒是否屬於當前程式。並且會持續跟蹤其真實的身份。
//在IPC機制中,這個方法用來清除IPCThreadState的pid和uid資訊。並且返回一個身份,便於使用restoreCallingIdentity()來恢復。
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
//重點(敲黑板)!這裡是個死迴圈,一直等待抽取訊息、傳送訊息。
Message msg = queue.next();
// 從MessageQueue中抽取一條訊息。至於怎麼取的,我們稍後再看。
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag; //取得MessageQueue的跟蹤標記
if (traceTag != 0) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
//開始跟蹤本執行緒的MessageQueue中的當前訊息,是Native的方法。
}
try {
msg.target.dispatchMessage(msg);
//嘗試分派訊息到和Message繫結的Handler中
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
//這個和Trace.traceBegin()配套使用。
}
}
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
final long newIdent = Binder.clearCallingIdentity();
//what?又呼叫這個Native方法了。這裡主要是為了再次驗證,執行緒所在的程式是否發生改變。
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycleUnchecked();
//回收釋放訊息。
}
}```
從上面的分析可以知道,當呼叫了Looper.loop()之後,執行緒就就會被一個for(;;)死迴圈阻塞,每次等待MessageQueue的next()方法取出一條Message才開始往下繼續執行。然後通過Message獲取到相應的Handler (就是target成員變數),Handler再通過dispatchMessage()方法,把Message派發到handleMessage()中處理。
這裡需要注意,當執行緒loop起來是時,執行緒就一直在迴圈中。就是說Looper.loop()後面的程式碼就不能被執行了。想要執行,需要先退出loop。
Looper myLooper = Looper.myLoop();
myLooper.quit(); //普通退出方式。
myLooper.quitSafely(); //安全的退出方式。```
現在又產生一個疑問,MessageQueue的next()方法是如何阻塞住執行緒的呢?接下來,扒一扒這個幕後黑手MessageQueue。
幕後黑手MessageQueue
MessageQueue是一個用單鏈的資料結構來維護訊息列表。
Message next() {
//檢查loop是否已經為退出狀態。mPrt是Native層的MessageQueue的地址。
//通過這個地址可以和Native層的MessageQueue互動。
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1;
int nextPollTimeoutMillis = 0;
//時間標記,當且僅當第一次獲取訊息時才為0。因為它在死迴圈外面啊!
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
//如果不是第一次獲取訊息,呼叫Native的函式,讓虛擬機器重新整理所有的餓Binder命令,
//確保程式在執行可能阻塞的任務之前,釋放之前的物件。
}
//這是一個Native的方法。
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) { //鎖住MessageQueue
//獲取當前的系統時間,用於後面和msg.when進行比較。
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//獲得當前MessageQueue中的第一條訊息
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
//這個判斷的意義在於只有到了Message應該被髮送的時刻才去傳送,否則繼續迴圈。
//計算下一條訊息的時間。注意最大就是Integer.MAX_VALUE。
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;
//返回一條訊息給Looper。
}
} else {
// 如果取到的Message為null,將時間標記設定為-1。
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;
}
}```
可以看到。MessageQueue在取訊息(呼叫next())時,會進入一個死迴圈,直到取出一條Message返回。這就是為什麼Looper.loop()會在queue.next()處等待的原因。
那麼,一條Message是如何新增到MessageQueue中呢?要弄明白最後的真相,我們需要調查一下mHandler.post()這個方法。
Handler究竟對Message做了什麼?
Handler的post()系列方法,最終呼叫的都是下面這個方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
//在這裡給Message的target賦值。
if (mAsynchronous) {
msg.setAsynchronous(true);
//如果是非同步,就標記為非同步
}
return queue.enqueueMessage(msg, uptimeMillis);
//就是這個方法把Message新增到執行緒的MessageQueue中的。
}```
接下來就看看MessageQueue的enqueueMessage()作了什麼。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
//沒Handler呼叫是會拋異常的啊
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
//不能使用一條正在使用中的Message。
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
//鎖住MessageQueue再往裡新增訊息。
if (mQuitting) {
//如果MessageQueue被標記為退出,就返回。
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();
//切換Message的使用狀態為未使用。
msg.when = when;
//我們設定的延遲傳送的時間。
//經過下面的邏輯,Message將會被“儲存”在MessageQueue中。
//實際上,Message在MessageQueue中的儲存方式,
//是使用Message.next逐個向後指向的單連結串列結構來儲存的。
//比如:A.next = B, B.next = C…
Message p = mMessages;
//嘗試獲取當前Message
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// 如果為null,說明是第一條。
msg.next = p;
mMessages = msg;
//設定當前的Message為傳入的Message,也就是作為第一條。
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
//不滿足作為第一條Message的條件時,通過下面的逐步變換,將它放在最後面。
//這樣便把Message“儲存”到MessageQueue中了。
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;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}```
至此,我們已經揭露了Looper、Handler、MessageQueue隱藏的祕密。
另一個疑問?
也許你已經注意到在主執行緒中可以直接使用Handler,而不需要Looper.prepare()和Looper.loop()。為什麼可以做到這樣呢?根據之前的分析可以知道,主執行緒中必然存在Looper.prepare()和Looper.loop()。既然如此,為什麼主執行緒沒有被loop()阻塞呢?看一下ActivityThread來弄清楚到底是怎麼回事。
//這個main()方法可以認為是Android應用的起點
public static void main(String[] args) {
。
。
。
Looper.prepareMainLooper();
//主要作用和我們平時呼叫的Looper.prepare()差不多
ActivityThread thread = new ActivityThread();
//建立本類例項
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
//重點啊!這裡取得了處理主執行緒事物的Handler。
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
//開始迴圈。可以看到,主執行緒本質上是阻塞的!
。
。
。
}```
注意ActivityThread並沒有繼承Thread,它的Handler是繼承Handler的私有內部類H.class。在H.class的handleMessage()中,它接受並執行主執行緒中的各種生命週期狀態訊息。UI的16ms的繪製也是通過Handler來實現的。也就是說,主執行緒中的所有操作都是在Looper.prepareMainLooper()和Looper.loop()之間進行的。進一步說是在主Handler中進行的。
總結
Android中Thread在建立時進行初始化,會使用當前執行緒作為父執行緒,並繼承它的一些配置。
Thread初始化時會被新增到指定/父執行緒的ThreadGroup中進行管理。
Thread正真啟動是一個native函式完成的。
在Android的執行緒間通訊中,需要先建立Looper,就是呼叫Looper.prepare()。這個過程中會自動依賴當前Thread,並且建立MessageQueue。經過上一步,就可以建立Handler了,預設情況下,Handler會自動依賴當前執行緒的Looper,從而依賴相應的MessageQueue,也就知道該把訊息放在哪個地方了。MessageQueue通過Message.next實現了一個單連結串列結構來快取Message。訊息需要送達Handler處理,還必須呼叫Looper.loop()啟動執行緒的訊息泵送迴圈。loop()內部是無限迴圈,阻塞在MessageQueue的next()方法上,因為next()方法內部也是一個無限迴圈,直到成功從連結串列中抽取一條訊息返回為止。然後,在loop()方法中繼續進行處理,主要就是把訊息派送到目標Handler中。接著進入下一次迴圈,等待下一條訊息。由於這個機制,執行緒就相當於阻塞在loop()這了。
相關文章
- 你知道Spring中BeanFactoryPostProcessors是如何執行的嗎?SpringBean
- jvm是如何執行i = i++ + ++i的,你知道嗎?JVM
- ScheduledThreadPoolExecutor原始碼分析-你知道定時執行緒池是如何實現延遲執行和週期執行的嗎?thread原始碼執行緒
- Thread(執行緒)thread執行緒
- 你真的知道計算機是如何進行減法運算的嗎?計算機
- 1v1影片原始碼,你知道如何實現多執行緒的順序執行嗎?原始碼執行緒
- Java 中的執行緒 threadJava執行緒thread
- 你的單例模式真的是執行緒安全的嗎?單例模式執行緒
- Spring Boot 到底是怎麼執行的,你知道嗎?Spring Boot
- Spring Boot到底是怎麼執行的,你知道嗎?Spring Boot
- Mybatis如何執行Select語句,你真的知道嗎?MyBatis
- 你知道SSL是如何工作的嗎?
- Java 多執行緒(Java.Thread)------ 執行緒協作(生產者消費者模式)Java執行緒thread模式
- new Thread與執行緒建立thread執行緒
- Thread執行緒終止interruptthread執行緒
- 執行緒池中多餘的執行緒是如何回收的?執行緒
- 【 Thread】建立執行緒的2種方法thread執行緒
- 知道執行緒池的四種拒絕策略嗎?執行緒
- 面試官問:多執行緒同步內部如何實現的,你知道怎麼回答嗎?面試執行緒
- 小夥子,你懂執行緒池的建立嗎?執行緒
- Java如何停止執行緒,確定你知道的都是正確的麼?Java執行緒
- 面試官:你確定 Redis 是單執行緒的程式嗎?面試Redis執行緒
- java多執行緒之Thread類Java執行緒thread
- Java多執行緒(二):Thread類Java執行緒thread
- Java多執行緒Thread類使用Java執行緒thread
- 總是在聊執行緒Thread,試試協程吧!執行緒thread
- c# 執行緒Thread的IsBackground屬性C#執行緒thread
- ConcurrentHashMap的size方法是執行緒安全的嗎?HashMap執行緒
- 你知道前端是如何實現水印的嗎前端
- 什麼是Python執行緒?Python執行緒如何建立?Python執行緒
- 你知道 koa 中介軟體執行原理嗎?
- 一. 執行緒管理之Thread基礎執行緒thread
- 多執行緒系列(二)之Thread類執行緒thread
- Thread執行緒知識點講解thread執行緒
- 執行緒池續:你必須要知道的執行緒池submit()實現原理之FutureTask!執行緒MIT
- Thread 中的 join() 方法的作用是呼叫執行緒等待該執行緒執行完後,再繼續執行thread執行緒
- java 多執行緒(關於Thread的講解)Java執行緒thread
- 想知道你的Mac可以執行哪些版本的macOS嗎?Mac
- Swift多執行緒:使用Thread進行多執行緒間通訊,協調子執行緒任務Swift執行緒thread