你知道Thread執行緒是如何運作的嗎?

玄學醬發表於2017-05-02

背景介紹

我們在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()這了。


相關文章