1. 程式人生 > >我的android多執行緒程式設計之路(1)之經驗詳解,原始碼分析

我的android多執行緒程式設計之路(1)之經驗詳解,原始碼分析

寫在伊始

android開發這麼久了,對於多執行緒這塊一直處於似懂非懂的神奇狀態,今天總結出來,分享一下,希望大家多多指正。共同交流,懇望得到您的建議。

本文簡介

本文會基於自己在開發中對於執行緒這塊的實際使用,大概從執行緒程序的概念,執行緒的建立(Thread和Runnable)和使用,執行緒的各個方法的介紹,執行緒池的介紹等,及Handler,AsyncTask,IntentService及現在使用的RxJava2.0(執行緒控制部分,對此部分的RxJava2.0原始碼分析費了好長時間,還請各位一起發現問題)進行總結,會加上自己對於原始碼的一些理解。本文可能較長,如果您對本文的內容有研究或者有興趣,還請認真觀看,並提出自己的一些建議和認識,我希望自己能從大家的身上認識到這塊的不足,在此謝謝大家了。

本文寫到最後,決定分為兩個部分,第一部分是除去RxJava2.0執行緒排程部分的全部內容。
第二部分全部用於對RxJava2.0執行緒排程(Schedulers)的原始碼進行分析。

執行緒與程序簡介

程序是指一個記憶體中執行的應用程式,每個程序都有自己獨立的一塊記憶體空間,一組系統資源。即程序空間或(虛空間)。在程序的概念中,每一個程序的內部資料和狀態都是完全獨立的。程序不依賴於執行緒而獨立存在,一個程序中可以啟動多個執行緒。在Windows作業系統中一個程序就是一個exe或dll程式,它們相互獨立,互相也可以通訊,在Android作業系統中程序間的通訊應用也是很多的(這個在這先不說)。

執行緒是指程序中的一個執行流程,一個程序中可以執行多個執行緒。執行緒與程序相似,是一段完成某個特定功能的程式碼,是程式中單個順序的流控制。但與程序不同的是,同類的多個執行緒共享一塊記憶體空間和一組系統資源,所以系統在各個執行緒之間切換時,資源佔用要比程序小得多,正因如此,執行緒也被稱為輕量級程序。執行緒總是屬於某個程序,執行緒沒有自己的虛擬地址空間,與程序內的其他執行緒一起共享分配給該程序的所有資源。值得注意的是,執行緒是不能夠獨立執行的,必須依存在應用程式中,由應用程式提供多個執行緒執行控制。
 

多執行緒簡介

多執行緒指的是在單個程式中可以同時執行多個不同的執行緒,執行不同的任務。多執行緒意味著一個程式的多行語句可以看上去幾乎在同一時間內同時執行。 “同時”執行是人的感覺,線上程之間實際上輪換執行。多執行緒最常用的使用就是處理各種各樣的耗時操作,提高程式的執行效率。

執行緒的兩種建立方式

在Java中有兩種方法實現執行緒體:一是繼承執行緒類Thread,二是實現介面Runnable。

Thread方式

public class FirstThread extends Thread {    
    @Override  
    public void run() {  //run方法,程式的執行程式碼
       ...
    }  
    public static void main(String[] args){  
       Thread t1=new FirstThread ();   
       t1.start();  
    }  
}  

Runnable方式(適合資源共享)

public class RunnableImpl implements Runnable{  
    @Override  
    public void run() {  //run方法,程式的執行程式碼
       ...     
    }  
}  

public class TestRunnable {  

    public static void main(String[] args) {  
       RunnableImpl ri=new RunnableImpl();  
       Thread t1=new Thread(ri);  
       t1.start();  
    }  
}  

執行緒的五種狀態及其轉換

執行緒的狀態轉換是執行緒控制的基礎。執行緒狀態總的可以分為五大狀態。分別為:

  • 新建:執行緒物件已經建立,還沒有在其上呼叫start()方法。
  • 就緒:當執行緒有資格執行,但排程程式還沒有把它選定為執行執行緒時執行緒所處的狀態。當start()方法呼叫時,執行緒首先進入可執行狀態。線上程執行之後或者從阻塞、等待或睡眠狀態回來後,也返回到可執行狀態。
  • 執行:執行緒排程程式從可執行池中選擇一個執行緒作為當前執行緒時執行緒所處的狀態。這也是執行緒進入執行狀態的唯一一種方式。
  • 等待:這是執行緒有資格執行時它所處的狀態。此時執行緒仍舊是活的,但是當前沒有條件執行。換句話說,它是可執行的,但是如果某件事件出現,他可能返回到可執行狀態。
  • 死亡:當執行緒的run()方法完成時就認為它死去。這個執行緒物件也許是活的,但是,它已經不是一個單獨執行的執行緒。執行緒一旦死亡,就不能復生。如果在一個死去的執行緒上呼叫start()方法,會丟擲java.lang.IllegalThreadStateException異常。

執行緒的方法及同步問題

睡眠Thread.sleep()

Thread.sleep(longmillis)和Thread.sleep(long millis, int nanos)靜態方法強制當前正在執行的執行緒休眠(暫停執行),以“減慢執行緒”。當執行緒睡眠時,它入睡在某個地方,在甦醒之前不會返回到可執行狀態。當睡眠時間到期,則返回到可執行狀態。

try {  
        Thread.sleep(1000); //也就是1秒  
     } catch (InterruptedException e) {  
        e.printStackTrace();   
     }  

注意:睡眠的位置:為了讓其他執行緒有機會執行,可以將Thread.sleep()的呼叫放執行緒run()之內。這樣才能保證該執行緒執行過程中會睡眠。

執行緒讓步yield()

執行緒的讓步是通過Thread.yield()來實現的。yield()方法的作用是:暫停當前正在執行的執行緒物件,並執行其他執行緒。 yield()應該做的是讓當前執行執行緒回到可執行狀態,以允許具有相同優先順序的其他執行緒獲得執行機會。因此,使用yield()的目的是讓相同優先順序的執行緒之間能適當的輪轉執行。但是,實際中無法保證yield()達到讓步目的,因為讓步的執行緒還有可能被執行緒排程程式再次選中。

執行緒加入join()

保證當前執行緒停止執行,直到該執行緒所加入的執行緒完成為止。然而,如果它加入的執行緒沒有存活,則當前執行緒不需要停止。比如,一個執行緒B“加入”到另外一個執行緒A的尾部。在A執行完畢之前,B不能工作。

執行緒中斷interrupt()

中斷某個執行緒,這種結束方式比較粗暴,如果t執行緒打開了某個資源還沒來得及關閉也就是run方法還沒有執行完就強制結束執行緒,會導致資源無法關閉

注意:另外還有wait(),notify()等多個方法。暫不介紹,後續會給一篇學習的好文。

執行緒同步(解決死鎖問題)

此概念主要解決多執行緒操作中的死鎖問題,及確定同一時間只有一個執行緒在執行操作,主要又兩種實現方式,一是同步程式碼塊,及鎖在run方法中,二是同步方法,及把synchronized當作函式修飾符。

1.同步程式碼塊

public void run() {  
       try {  
           Thread.sleep(1000);  
           synchronized (this) { //此方式解決鎖問題 
            ...
           }           
           Thread.sleep(1000);  
       } catch (InterruptedException e) {  
           e.printStackTrace();  
       }  
    }  

2.同步方法

Public synchronized void method(){//此方式解決鎖問題
    ...
}

向下會總結執行緒池及在android工作中對於執行緒方面的使用問題

執行緒池的簡介及好處

多執行緒開發中,由於執行緒數量多,並且每個執行緒執行一段時間就結束,所以要頻繁的建立執行緒,但是這樣頻繁的建立執行緒會大大降低系統的效率,因為頻繁建立執行緒和銷燬執行緒需要時間。在這種情況下,人們就想要一種可以執行緒執行完後不用銷燬,同時該執行緒還可以去執行其他任務,在這樣的情況下執行緒池就出現了。線上程池的程式設計模式下,任務是提交給整個執行緒池,而不是直接交給某個執行緒,執行緒池在拿到任務後,它就在內部找有無空閒的執行緒,再把任務交給內部某個空閒的執行緒,如果沒有空閒的程序,任務就處於等待狀態,這就是封裝。使用了執行緒池後減少了建立和銷燬執行緒的次數,每個執行緒都可以被重複利用,可執行多個任務;同時可以根據系統的承受能力,調整執行緒池中執行緒的數目,避免出現將系統記憶體消耗完畢這樣的情況出現。

總結來說,他的好處有以下三個方面:

  • 降低資源消耗:通過重用已經建立的執行緒來降低執行緒建立和銷燬的消耗
  • 提高響應速度:任務到達時不需要等待執行緒建立就可以立即執行。
  • .提高執行緒的可管理性:執行緒池可以統一管理、分配、調優和監控。

執行緒池詳解

java的執行緒池支援主要通過ThreadPoolExecutor來實現,我們使用的ExecutorService的各種執行緒池策略都是基於ThreadPoolExecutor實現的,所以ThreadPoolExecutor十分重要。

此繼承關係如下:繼承關係: Executor -> ExecutorService -> AbstractExecutorService -> ThreadPoolExecutor

從ThreadPoolExecutor原始碼認識其建立:

public class ThreadPoolExecutor extends AbstractExecutorService {  

        ...//註釋省略
        public ThreadPoolExecutor(int corePoolSize,  
                                  int maximumPoolSize,  
                                  long keepAliveTime,  
                                  TimeUnit unit,  
                                  BlockingQueue<Runnable> workQueue) {  
            this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,  
                 Executors.defaultThreadFactory(), defaultHandler);  
        }  

        ...//註釋省略
        public ThreadPoolExecutor(int corePoolSize,  
                                  int maximumPoolSize,  
                                  long keepAliveTime,  
                                  TimeUnit unit,  
                                  BlockingQueue<Runnable> workQueue,  
                                  ThreadFactory threadFactory) {  
            this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,  
                 threadFactory, defaultHandler);  
        }  

        ...//註釋省略
        public ThreadPoolExecutor(int corePoolSize,  
                                  int maximumPoolSize,  
                                  long keepAliveTime,  
                                  TimeUnit unit,  
                                  BlockingQueue<Runnable> workQueue,  
                                  RejectedExecutionHandler handler) {  
            this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,  
                 Executors.defaultThreadFactory(), handler);  
        }  

        ...//註釋省略  最全
        public ThreadPoolExecutor(int corePoolSize,  
                                  int maximumPoolSize,  
                                  long keepAliveTime,  
                                  TimeUnit unit,  
                                  BlockingQueue<Runnable> workQueue,  
                                  ThreadFactory threadFactory,  
                                  RejectedExecutionHandler handler) {  
            if (corePoolSize < 0 ||  
                maximumPoolSize <= 0 ||  
                maximumPoolSize < corePoolSize ||  
                keepAliveTime < 0)  
                throw new IllegalArgumentException();  
            if (workQueue == null || threadFactory == null || handler == null)  
                throw new NullPointerException();  
            this.corePoolSize = corePoolSize;  
            this.maximumPoolSize = maximumPoolSize;  
            this.workQueue = workQueue;  
            this.keepAliveTime = unit.toNanos(keepAliveTime);  
            this.threadFactory = threadFactory;  
            this.handler = handler;  
        }  

引數介紹:

corePoolSize- 池中所儲存的核心執行緒數,包括空閒執行緒,預設情況下,在建立了執行緒池後,執行緒池中的執行緒數為0,當有任務來之後,就會建立一個執行緒去執行任務,當執行緒池中的執行緒數目達到corePoolSize後,就會把到達的任務放到快取隊列當中;

maximumPoolSize -池中允許的最大執行緒數。

keepAliveTime- 當執行緒數大於核心數時,執行緒沒有任務執行時最多保持多久時間就會終止,預設情況下,只有當執行緒池中的執行緒數大於corePoolSize時,keepAliveTime才會起作用;如果執行緒池中的執行緒數小於corePoolSize,一個執行緒空閒的時間達到keepAliveTime,就會終止

unit -引數keepAliveTime的時間單位,在TimeUnit類中有有七種靜態屬性:

    TimeUnit.DAYS;               //天  
    TimeUnit.HOURS;             //小時  
    TimeUnit.MINUTES;           //分鐘  
    TimeUnit.SECONDS;           //秒  
    TimeUnit.MILLISECONDS;      //毫秒  
    TimeUnit.MICROSECONDS;      //微妙  
    TimeUnit.NANOSECONDS;       //納秒  

workQueue- 執行前用於保持任務的佇列,也就是用來儲存等待執行的任務。此佇列僅保持由 execute 方法提交的 Runnable 任務。一般我們也稱它為阻塞佇列,這個引數的選擇也很重要,會對執行緒池的執行過程產生重大影響。

threadFactory -執行程式建立新執行緒時使用的工廠;

handler -由於超出執行緒範圍和佇列容量而使執行被阻塞時所使用的處理程式,常見有以下幾種:

ThreadPoolExecutor.AbortPolicy:放棄任務拋異常。
ThreadPoolExecutor.DiscardPolicy:放棄任務不拋異常。
ThreadPoolExecutor.DiscardOldestPolicy:放棄佇列最前面的任務,然後重新嘗試執行任務(重複此過程)
ThreadPoolExecutor.CallerRunsPolicy:呼叫執行緒處理該任務

執行緒池的建立

JDK中,java.util.concurrent.Executors類,提供了建立四種執行緒池的方法。(你會發現,RxJava中使用的也是這幾種執行緒池,這部分原始碼會在後面講解,我們先了解一下

newFixedThreadPool

newFixedThreadPool,用來建立一個定長執行緒池,可控制執行緒最大併發數,超出的執行緒會在佇列中等待。定長執行緒池的大小通常根據系統資源進行設定:Runtime.getRuntime().availableProcessors()。

public class NewFixedThreadPoolTest {  
    public static void main(String[] args) {  
        // 建立一個可重用固定執行緒數的執行緒池  
        ExecutorService pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); //也可自己指定大小 
        // 建立執行緒  Runtime.getRuntime().availableProcessors()的個數 我這寫三個
        Thread t1 = new MyThread();  
        Thread t2 = new MyThread();  
        Thread t3 = new MyThread();  
        // 將執行緒放入池中進行執行  
        pool.execute(t1);  
        pool.execute(t2);  
        pool.execute(t3);  
        // 關閉執行緒池  
        pool.shutdown();  
    }  
}  

class MyThread extends Thread {  
    @Override  
    public void run() {  
        System.out.println( "正在執行"+Thread.currentThread().getName());  
    }  
}  

newScheduledThreadPool

newScheduledThreadPool來建立一個定長執行緒池,並且支援定時和週期性的執行任務(其餘程式碼同第一條)

    //定時執行 
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(20);  // 長度20  
    scheduledThreadPool.schedule(task, 10, TimeUnit.SECONDS);   // 延遲10s執行  

    //週期性執行 
    ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(20);  // 長度20  
    scheduledThreadPool.scheduleAtFixedRate(task,10, 5, TimeUnit.SECONDS); // 延遲10s執行,每個5s執行一次。  

newCachedThreadPool

newCachedThreadPool用來建立一個可快取執行緒池,該執行緒池沒有長度限制,對於新的任務,如果有空閒的執行緒,則使用空閒的執行緒執行,如果沒有,則新建一個執行緒來執行任務。如果執行緒池長度超過處理需要,可靈活回收空閒執行緒。

    //其餘同第一條
    ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); 

newSingleThreadExecutor

SingleThreadExecutor模式只會建立一個執行緒。它和FixedThreadPool比較類似,不過執行緒數是一個。如果多個任務被提交給SingleThreadExecutor的話,那麼這些任務會被儲存在一個佇列中,並且會按照任務提交的順序,一個先執行完成再執行另外一個執行緒。
SingleThreadExecutor模式可以保證只有一個任務會被執行。這種特點可以被用來處理共享資源的問題.

    //其餘同第一條
    ExecutorService pool = Executors.newSingleThreadExecutor();

自定義執行緒池

當然我們也可以自定義執行緒池,說實話,我沒用過,還請自行百度。嘿嘿。我這隻說明這是可以的。

多執行緒操作之Handler

handler的優缺點:handler對於對後臺任務時,簡單清晰,但是handler對於操作單個後臺任務,程式碼過於繁瑣。

對於handler的使用操作,我不想多說了,其實就是在主執行緒中建立Handler物件並實現handlmessage()方法,建立runnable執行緒,先線上程中執行耗時操作,開啟一個執行緒會相應的產生一個looper,在初始化looper的時候會建立一個訊息佇列MessageQueue();執行完耗時操作,通過handler將訊息傳送到訊息佇列中、、looper輪詢訊息佇列將訊息取出來交給Handler,Handler接收到取出來的訊息,並根據訊息型別做出相應的處理。

下面我會說一些自己對handle對於原始碼的理解,有不對還請指出

弄清楚handler,就要弄清楚這四個東西,handler Message Looper MessageQueue。

首先,我們從Looper 輪詢器和MessageQueue的建立說起:

先宣告下,主執行緒的Looper不需要程式設計師建立的,系統會建立,通過prepareMainLooper()->Looper.Prepare(); 然後會通過一個threadLocal(執行緒單例,一個執行緒就有一個物件) 把Looper物件和當前執行緒建立起一一對應的關係。Looper.Prepare()會呼叫Looper的private構造方法 並且把建立的looper物件儲存到threadLocal中。在Looper構造中會建立一個MessageQueue物件 並且通過一個final型別的成員變數把MessageQueue儲存起來。這樣確保一個Looper對應唯一的messageQueue。所以 一個執行緒最多隻能有一個looper, 一個looper對應唯一的MessageQueueu 也就是一個執行緒有唯一的Looper唯一的messageQueue。

MessageQueue在建立的過程中會呼叫nativeInit方法 創建出一個nativeMessageQueue,建立NativeMessaageQueue的時候還會建立一個C++的Looper,java層的MessageQueue和NativeMessageQueue通過一個成員變數mPtr建立起關聯 mPtr儲存了nativeMessageQueue的指標。

說了這麼多,是時候看一眼原始碼了,請對應上述描述一起觀看。

Looper.prepareMainLooper(),他會呼叫prepare方法:

這裡寫圖片描述

Looper.prepare():建立的looper物件儲存到threadLocal

這裡寫圖片描述

MessageQueue和NativeMessageQueue的建立:

這裡寫圖片描述

這裡寫圖片描述

需要注意 如果在主執行緒中呼叫Looper.prepareMainLooper或者Looper.prepare() 程式會丟擲異常。

Looper.loop讓訊息迴圈

Looper取訊息的程式碼中,有一個死迴圈,需要理解為什麼要有死迴圈?死迴圈為什麼不會阻塞主執行緒?

看一下loop原始碼,裡面有一個for迴圈。那麼先來解釋下再看原始碼吧,對於主執行緒,也就是我們的android程式,我們是絕不希望會被執行一段時間,自己就退出,那麼如何保證能一直存活呢?簡單做法就是可執行程式碼是能一直執行下去的,死迴圈便能保證不會被退出,例如,binder執行緒也是採用死迴圈的方法,通過迴圈方式不同與Binder驅動進行讀寫操作,當然並非簡單地死迴圈,無訊息時會休眠。

對於不會阻塞主執行緒的問題?

真正會卡死主執行緒的操作是在回撥方法onCreate/onStart/onResume等操作時間過長,會導致掉幀,甚至發生ANR,looper.loop本身不會導致應用卡死。這裡會涉及到linux中一個管道(pipe)的概念。他的原理:在記憶體中有一個特殊的檔案,這個檔案有兩個控制代碼(引用),一個是讀取控制代碼,一個是寫入控制代碼。原因簡單說就是在主執行緒的MessageQueue沒有訊息時,便阻塞在loop的queue.next()中的nativePollOnce()方法裡(下面原始碼中會有標記),此時主執行緒會釋放CPU資源進入休眠狀態,直到下個訊息到達或者有事務發生,通過往pipe管道寫端寫入資料來喚醒主執行緒工作。這裡採用的epoll機制,是一種IO多路複用機制,可以同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則立刻通知相應程式進行讀或寫操作,本質同步I/O,即讀寫是阻塞的。 所以說,主執行緒大多數時候都是處於休眠狀態,並不會消耗大量CPU資源,也不會造成主執行緒的阻塞。

        public static void loop() {
6.        final Looper me = myLooper();
7.        if (me == null) {
8.            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
9.        }
10.        final MessageQueue queue = me.mQueue;
11.
12.        // Make sure the identity of this thread is that of the local process,
13.        // and keep track of what that identity token actually is.
14.        Binder.clearCallingIdentity();
15.        final long ident = Binder.clearCallingIdentity();
16.
17.        for (;;) {
18.            Message msg = queue.next(); // might block 可能會阻塞 就是到訊息佇列中取出下一條訊息
19.            if (msg == null) {
20.                // No message indicates that the message queue is quitting.
21.                return;
22.            }
23.
24.            // This must be in a local variable, in case a UI event sets the logger
25.            Printer logging = me.mLogging;
26.            if (logging != null) {
27.                logging.println(">>>>> Dispatching to " + msg.target + " " +
28.                        msg.callback + ": " + msg.what);
29.            }
30.
31.            msg.target.dispatchMessage(msg);
32.
33.            if (logging != null) {
34.                logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
35.            }
36.
37.            // Make sure that during the course of dispatching the
38.            // identity of the thread wasn't corrupted.
39.            final long newIdent = Binder.clearCallingIdentity();
40.            if (ident != newIdent) {
41.                Log.wtf(TAG, "Thread identity changed from 0x"
42.                        + Long.toHexString(ident) + " to 0x"
43.                        + Long.toHexString(newIdent) + " while dispatching to "
44.                        + msg.target.getClass().getName() + " "
45.                        + msg.callback + " what=" + msg.what);
46.            }
47.
48.            msg.recycle();
49.        }
50.    }

MessageQueue的next方法

      final Message next() {
3.        int pendingIdleHandlerCount = -1; // -1 only during first iteration
4.        int nextPollTimeoutMillis = 0;
5.
6.        for (;;) {
7.            if (nextPollTimeoutMillis != 0) {
8.                Binder.flushPendingCommands();
9.            }
10.            nativePollOnce(mPtr, nextPollTimeoutMillis);
11.            //這裡會阻塞 用到linux底層的pipe 和epoll機制 傳入兩個引數
11.            //第一個引數 nativeMessageQueue的指標 第二個引數就是訊息超時時間
12.            // Linux的一個程序間通訊機制:管道(pipe)。原理:在記憶體中有一個特殊的檔案,這個檔案有兩個控制代碼(引用),一個是讀取控制代碼,一個是寫入控制代碼
13.            synchronized (this) {
14.                if (mQuiting) {
15.                    return null;
16.                }
17.
18.                // Try to retrieve the next message.  Return if found.
19.                final long now = SystemClock.uptimeMillis();
20.                Message prevMsg = null;
21.                Message msg = mMessages;
22.                if (msg != null && msg.target == null) {
23.                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
24.                    do {
25.                        prevMsg = msg;
26.                        msg = msg.next;
27.                    } while (msg != null && !msg.isAsynchronous());
28.                }
29.                if (msg != null) {
30.                    if (now < msg.when) {
31.                        // Next message is not ready.  Set a timeout to wake up when it is ready.
32.                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
33.                    } else {
34.                        // Got a message.
35.                        mBlocked = false;
36.                        if (prevMsg != null) {
37.                            prevMsg.next = msg.next;
38.                        } else {
39.                            mMessages = msg.next;
40.                        }
41.                        msg.next = null;
42.                        if (false) Log.v("MessageQueue", "Returning message: " + msg);
43.                        msg.markInUse();
44.                        return msg;
45.                    }
46.                } else {
47.                    // No more messages.
48.                    nextPollTimeoutMillis = -1;
49.                }
50.
51.                // If first time idle, then get the number of idlers to run.
52.                // Idle handles only run if the queue is empty or if the first message
53.                // in the queue (possibly a barrier) is due to be handled in the future.
54.                if (pendingIdleHandlerCount < 0
55.                        && (mMessages == null || now < mMessages.when)) {
56.                    pendingIdleHandlerCount = mIdleHandlers.size();
57.                }
58.                if (pendingIdleHandlerCount <= 0) {
59.                    // No idle handlers to run.  Loop and wait some more.
60.                    mBlocked = true;
61.                    continue;
62.                }
63.
64.                if (mPendingIdleHandlers == null) {
65.                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
66.                }
67.                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
68.            }
69.
70.            // Run the idle handlers.
71.            // We only ever reach this code block during the first iteration.
72.            for (int i = 0; i < pendingIdleHandlerCount; i++) {
73.                final IdleHandler idler = mPendingIdleHandlers[i];
74.                mPendingIdleHandlers[i] = null; // release the reference to the handler
75.
76.                boolean keep = false;
77.                try {
78.                    keep = idler.queueIdle();
79.                } catch (Throwable t) {
80.                    Log.wtf("MessageQueue", "IdleHandler threw exception", t);
81.                }
82.
83.                if (!keep) {
84.                    synchronized (this) {
85.                        mIdleHandlers.remove(idler);
86.                    }
87.                }
88.            }
89.
90.            // Reset the idle handler count to 0 so we do not run them again.
91.            pendingIdleHandlerCount = 0;
92.
93.            // While calling an idle handler, a new message could have been delivered
94.            // so go back and look again for a pending message without waiting.
95.            nextPollTimeoutMillis = 0;
96.        }
97.    }

通過handler處理訊息

msg.target.dispatchMessage();msg的callback是一個runnable物件 如果msg的callback不為空,訊息交給這個callback處理.
如果msg的callback為空,則判斷handler的mCallback介面,這個介面中就一個方法handleMessage。如果這個callback不為空交給這個handleMessage處理
如果上面兩個都為空才交給handler的handleMessage處理訊息(也就是我們在程式碼中寫的方法)

     /**
2.     * Handle system messages here.
3.     */
4.    public void dispatchMessage(Message msg) {
5.        if (msg.callback != null) {
6.            handleCallback(msg);
7.        } else {
8.            if (mCallback != null) {
9.                if (mCallback.handleMessage(msg)) {
10.                    return;
11.                }
12.            }
13.            handleMessage(msg);
14.        }
15.    }

handler的建立

    public Handler(Callback callback, boolean async) {
2.        if (FIND_POTENTIAL_LEAKS) {
3.            final Class<? extends Handler> klass = getClass();
4.            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()&&(klass.getModifiers() & Modifier.STATIC) == 0) {       
7.                    klass.getCanonicalName());
8.            }
9.        }
10.
11.        mLooper = Looper.myLooper();
12.        if (mLooper == null) {
13.            throw new RuntimeException(
14.                "Can't create handler inside thread that has not called Looper.prepare()");
15.        }
16.        mQueue = mLooper.mQueue;
17.        mCallback = callback;
18.        mAsynchronous = async;
19.    }

建立handler的時候 先到當前的執行緒中獲取looper 如果當前執行緒沒有looper的話那麼會拋異常。
如果當前執行緒以經建立了looper那麼把這個Looper儲存到一個final型別的成員變數中 通過這個Looper找到對應的messageQueue,通過final成員變數儲存這個MessageQueue 這樣 確保在哪個執行緒建立的handler訊息會發送到對應執行緒的MessageQueue中,如果在子執行緒中使用handler 必須先呼叫Looper.prepare();再建立handler Looper.Loop()讓訊息佇列迴圈起來。子執行緒中使用訊息機制可以重複利用執行緒 。

通過handler傳送訊息

handler傳送訊息 sendMessage sendEmptyMessage… 實際上都是呼叫 sendMessageAtTime這個方法
sendMessageAtTime呼叫了enqueueMessage這個方法 這個方法實際上就是把訊息放到訊息佇列的過程

1.  private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
2.        msg.target = this;
3.        if (mAsynchronous) {
4.            msg.setAsynchronous(true);
5.        }
6.        return queue.enqueueMessage(msg, uptimeMillis);
7.    }

訊息佇列 enqueueMessage

1.
2.    final boolean enqueueMessage(Message msg, long when) {
3.        if (msg.isInUse()) {
4.            throw new AndroidRuntimeException(msg + " This message is already in use.");
5.        }
6.        if (msg.target == null) {
7.            throw new AndroidRuntimeException("Message must have a target.");
8.        }
9.
10.        boolean needWake;
11.        synchronized (this) {
12.            if (mQuiting) {
13.                RuntimeException e = new RuntimeException(
14.                        msg.target + " sending message to a Handler on a dead thread");
15.                Log.w("MessageQueue", e.getMessage(), e);
16.                return false;
17.            }
18.
19.            msg.when = when;  //重要
20.            Message p = mMessages;
21.            //msg.next 重要
21.            if (p == null || when == 0 || when < p.when) {
22.                // New head, wake up the event queue if blocked.
23.                msg.next = p;
24.                mMessages = msg;
25.                needWake = mBlocked;
26.            } else {
27.                // Inserted within the middle of the queue.  Usually we don't have to wake
28.                // up the event queue unless there is a barrier at the head of the queue
29.                // and the message is the earliest asynchronous message in the queue.
30.                needWake = mBlocked && p.target == null && msg.isAsynchronous();
31.                Message prev;
32.                for (;;) {
33.                    prev = p;
34.                    p = p.next;
35.                    if (p == null || when < p.when) {
36.                        break;
37.                    }
38.                    if (needWake && p.isAsynchronous()) {
39.                        needWake = false;
40.                    }
41.                }
42.                msg.next = p; // invariant: p == prev.next
43.                prev.next = msg;
44.            }
45.        }
46.        if (needWake) {
47.            nativeWake(mPtr);
48.        }
49.        return true;
50.    }

訊息如何在訊息佇列中排序?

上面原始碼中我標記了兩個重要的註釋,他們決定了這個問題。
實際上Messagequeue通過 一個成員變數 mMessage儲存了訊息佇列的第一條訊息 訊息在訊息佇列中的排序是根據訊息要執行的時間(就是when)先後順序進行排序,先執行的訊息排在前面 下一條訊息通過message的next屬性進行儲存 。

這裡寫圖片描述

enqueueMessage 就是根據新加入進來的訊息 要執行的時間跟已有的訊息進行比較找到合適的位置放到訊息佇列中。如果訊息需要立即執行,會執行nativeWake,實際上就是向管道中寫了一個w,那麼messageQueue的next方法就不會阻塞可以取出訊息。

    //沒錯 就這兩行程式碼
    if (needWake) {
            nativeWake(mPtr);
        }

最後,講一下message(訊息)的建立和回收

public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;//從訊息池中取出第一條訊息
                sPool = m.next; //把當前訊息池的第二條訊息作為訊息池的第一條
                m.next = null;//要去除的訊息的下一條置null
                sPoolSize--;//訊息池大小-1
                return m;//把這條訊息返回去
            }
        }
        return new Message();
    }

如果使用obtain方法來獲取訊息 那麼就會利用到android的訊息池 注意這個訊息池是全域性的 訊息池的大小50條
訊息的回收 在Looper.loop()方法中 當Handler處理訊息之後 會呼叫message.recycle()方法回收訊息

 public void recycle() {
       clearForRecycle();//把要回收的訊息 所有的成員變數恢復到剛new出來的狀態
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {//把這條訊息放到訊息池的第一條訊息
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

void clearForRecycle() {
        flags = 0;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        when = 0;
       target = null;
        callback = null;
        data = null;
    }

到這裡handler就說完了,感覺寫的好亂啊,不過湊活看吧,應該是可以看明白的,是嗎?請瘋狂call 1。

多執行緒操作之AsyncTask

AsyncTask的優缺點:AsyncTask操作簡單方便,過程可控,但是AsyncTask對於多非同步操作更新UI會變得很繁瑣。

對於使用程式碼,本文不提,如想使用,請自行百度。只講下具體操作,onPreExecute()執行在主執行緒中,開啟執行緒前的準備操作,doInBackground()執行在子執行緒中,onPreExecute()之後的操作,用於處理耗時操作,通過呼叫publishProcess()向 onProcessUpdata()推送訊息,onProcessUpdata()執行在主執行緒中,當呼叫 publishProcess()方法時就會開啟此方法,接收到推送過來的資料,更新UI進度頁面
onPostExecute()執行在主執行緒中,當子執行緒耗時操作執行完畢後會呼叫此方法, doInBackground()返回的引數傳遞到這裡來用於更新UI。呼叫execute()方法開啟AsyncTask,類似runnable的start()方法

下面我會說一些自己對AsyncTask對於原始碼的理解,有不對還請指出

看了原始碼才知道,真心的,AsyncTask實際上就是對handler的封裝。只是用到了執行緒池。說真的,不想寫了,你會看到,他的方法中全是通過Handler 做的一些操作。具體的還請自行觀看,如果您理解了handler,這個也就理解了。

private static Handler getHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler();
            }
            return sHandler;
        }
    }

但是會說一下他的執行緒池SerialExecutor。可以看到,SerialExecutor是使用ArrayDeque這個佇列來管理Runnable物件的,如果我們一次性啟動了很多個任務,首先在第一次執行execute()方法的時候,會呼叫ArrayDeque的offer()方法將傳入的Runnable物件新增到佇列的尾部,然後判斷mActive物件是不是等於null,第一次運行當然是等於null了,於是會呼叫scheduleNext()方法。在這個方法中會從佇列的頭部取值,並賦值給mActive物件,然後呼叫THREAD_POOL_EXECUTOR去執行取出的取出的Runnable物件。之後如何又有新的任務被執行,同樣還會呼叫offer()方法將傳入的Runnable新增到佇列的尾部,但是再去給mActive物件做非空檢查的時候就會發現mActive物件已經不再是null了,於是就不會再呼叫scheduleNext()方法。

那麼後面新增的任務豈不是永遠得不到處理了?當然不是,看一看offer()方法裡傳入的Runnable匿名類,這裡使用了一個try finally程式碼塊,並在finally中呼叫了scheduleNext()方法,保證無論發生什麼情況,這個方法都會被呼叫。也就是說,每次當一個任務執行完畢後,下一個任務才會得到執行,SerialExecutor模仿的是單一執行緒池的效果,如果我們快速地啟動了很多工,同一時刻只會有一個執行緒正在執行,其餘的均處於等待狀態。

    private static class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public 
            
           

相關推薦

android執行程式設計1經驗原始碼分析

寫在伊始 android開發這麼久了,對於多執行緒這塊一直處於似懂非懂的神奇狀態,今天總結出來,分享一下,希望大家多多指正。共同交流,懇望得到您的建議。 本文簡介 本文會基於自己在開發中對於執行緒這塊的實際使用,大概從執行緒程序的概念,執行緒的建立(T

Java執行程式設計學習總結

  (尊重勞動成果,轉載請註明出處:https://blog.csdn.net/qq_25827845/article/details/84894463冷血之心的部落格) 系列文章: Java多執行緒程式設計學習總結(一) Java多執行緒程式設計學習總結(二) 前

Android執行-----併發和同步volatile

volatile是Java提供的一種輕量級的同步機制,在併發程式設計中,它也扮演著比較重要的角色。同synchronized相比(synchronized通常稱為重量級鎖),volatile更輕量級,相比使用synchronized所帶來的龐大開銷,倘若能恰當的合理的使用volatile,自然是好事

Android執行-----併發和同步Lock

一、為什麼需要Lock 如果一個程式碼塊被synchronized修飾了,當一個執行緒獲取了對應的鎖,並執行該程式碼塊時,其他執行緒便只能一直等待,等待獲取鎖的執行緒釋放鎖,而這裡獲取鎖的執行緒釋放鎖只會有兩種情況: 1)獲取鎖的執行緒執行完了該程式碼塊,然後執行緒釋放對鎖的佔有; 2)執行緒執

Android執行-----併發和同步synchronized

一、鎖 物件的內建鎖和物件的狀態之間是沒有內在的關聯的,雖然大多數類都將內建鎖用做一種有效的加鎖機制,但物件的域並不一定通過內建鎖來保護。當獲取到與物件關聯的內建鎖時,並不能阻止其他執行緒訪問該物件,當某個執行緒獲得物件的鎖之後,只能阻止其他執行緒獲得同一個鎖。之所以每個物件都有一個內建鎖,是為

Android執行-----併發和同步ThreadLocal

一.對ThreadLocal的理解        很多地方叫做執行緒本地變數,也有些地方叫做執行緒本地儲存,其實意思差不多。可能很多朋友都知道ThreadLocal為變數在每個執行緒中都建立了一個副本,那麼每個執行緒可以訪問自己內部的副本變數,也就是進行資

Java 執行程式設計學習總結

定義篇 程序(Process)和執行緒(Thread) 怎樣實現多工處理(Multitasking)? 多工處理是同時執行多個任務的過程。我們使用多工處理來利用 CPU。可通過兩種方式實現多工處理: · 基於程序的多工 (多重處理) · 基於執行緒的多工處理

執行程式設計——基礎篇

  [寫在前面]   隨著計算機技術的發展,程式設計模型也越來越複雜多樣化。但多執行緒程式設計模型是目前計算機系統架構的最終模型。隨著CPU主頻的不斷攀升,X86架構的硬體已經成為瓶,在這種架構的CPU主頻最高為4G。事實上目前3.6G主頻的CPU已經接近了頂峰。   如果不

聊聊執行哪一些事兒task

  多執行緒,一個多麼熟悉的詞彙,作為一名程式設計師,我相信無論是從事什麼開發語言,都能夠輕輕鬆鬆說出幾種實現多執行緒的方式,並且在實際工作種也一定用到過多執行緒,比如:定時器、非同步作業等等,如果你說你沒有用過多執行緒,我懷疑你是不是一名程式設計師,哈哈。     哈

聊聊執行哪一些事兒task 二 延續操作

hello,又見面啦,昨天我們簡單的介紹瞭如何去建立和執行一個task、如何實現task的同步執行、如何阻塞等待task集合的執行完畢等待,昨天講的是task的最基本的知識點,如果你沒有看昨天的部落格,也不要急,你可以點選下面的地址, 聊聊多執行緒哪一些事兒(task)之 一),先看看後,在回到這兒

聊聊執行那一些事兒task 三 非同步取消和非同步方法

   hello,咋們又見面啦,通過前面兩篇文章的介紹,對task的建立、執行、阻塞、同步、延續操作等都有了很好的認識和使用,結合實際的場景介紹,這樣一來在實際的工作中也能夠解決很大一部分的關於多執行緒的業務,但是隻有這一些是遠遠不夠的,比如,比如,如果這麼一個場景,當開啟tsak非同步任務後,有某

執行訪問共享資料1

多執行緒訪問共享資料解決方案: 一,什麼是多執行緒  執行緒是程式中一個單一的順序控制流程.在單個程式中同時執行多個執行緒完成不同的工作,稱為多執行緒.  所有的執行緒雖然在微觀上是序列執行的,但是在巨集觀上你完全可以認為它們在並行執行 二,多執行緒訪問共享資料解決方

android執行程式設計2RxJava Schedulers原始碼分析

寫在伊始 上一篇介紹了執行緒的一些基礎知識和工作這麼久以後對於多執行緒部分的使用經驗之路,這篇主要對RxJava執行緒控制部分進行分析。 RxJava(本文就RxJava2.0分析) 說實話,近一年多一直在用rxjava進行專案架構的編寫及封裝及一些非

Android 執行程式設計 HandlerThread

    HandlerThread有那些特點: HandlerThread本質上是一個執行緒類,它繼承了Thread; HandlerThread有自己的內部Looper物件,可以進行looper迴圈; 通過獲取HandlerThread的looper物件傳

Android執行程式設計————微信頁面載入示例

為什麼使用多執行緒?        因為所有程式碼都預設執行在主執行緒當中,所以當程式執行需要執行一些耗時操作,如發起一條網路請求時,由於網速等原因,伺服器未必立刻響應請求,如果不將這類操作放在子執行緒裡去執行,就會導致主執行緒被阻塞住。  

Android 執行程式設計:Handler訊息傳遞機制—重新整理UI主介面

一、為什麼使用Handler   當一個Activity執行的時候,會開啟一條主執行緒,主執行緒主要負責處理與UI相關的事件,主執行緒不允許其他子執行緒操控它,更新UI介面。既然不允許我們在子執行緒中操控UI介面,那麼,像我們平時所見的點選獲取驗證碼,不斷更新

Android執行程式設計

程式語言中的多執行緒程式設計始終是程式設計師的難題,現在整理的內容是通過書籍網路及平時的工作經驗所得,寫出來供自己和同行的你日後工作中作為參考。 疑難雜症:        病症1:當我們要執行耗時的操作,比如發起網路請求時,考慮到網速等原因,伺服器未必會立刻響應。 基本處方

《Java執行程式設計實戰》——第1章 Java執行程式設計實戰基礎

Java執行緒: 守護執行緒——不影響JVM的正常停止,常用於執行一些重要性不太高的任務 使用者執行緒—— 建立一個Thread例項與建立其他類例項的區別: JVM為Thread例項分配兩個呼叫棧所需的儲存空間: 跟蹤Java程式碼間的呼叫關係

java執行程式設計核心技術學習-1

實現多執行緒的兩種方式 繼承Thread類,重寫Thread類中的run方法 public class MyThread extends Thread{ @Override public void run(){ super.run();

【新聞】本人新書《Java執行程式設計實戰指南核心篇》已出版上市

豆瓣主頁 購買連結 試讀下載 (待補充) 原始碼下載 內容簡介 隨著現代處理器的生產工藝從提升處理器主頻頻率轉向多核化,即在一塊晶片上整合多個處理器核心(Core),多核處理器(Multicore Proc