Android的線程和線程池

導語

在Android系統,線程主要分為主線程和子線程,主線程處理和界面相關的事情,而子線程一般用于執行耗時操作。頻繁創建銷毀線程不明智,使用線程池是正確的做法。線程池會緩存一定數量的線程,通過線程池就可以避免因為頻繁創建和銷毀線程所帶來的系統開銷。AsyncTask底層是線程池,IntentService/HandlerThread底層是線程。

主要內容

  • 主線程和子線程
  • Android中的線程形態
  • Android線程池

具體內容

在Android中,線程的形態有很多種:

  • AsyncTask封裝了線程池和Handler。
  • HandlerThread是具有消息循環的線程,內部可以使用handler
  • IntentService是一種Service,內部采用HandlerThread來執行任務,當任務執行完畢后IntentService會自動退出。由于它是一種Service,所以不容易被系統殺死。

操作系統中,線程是操作系統調度的最小單元,同時線程又是一種受限的系統資源,其創建和銷毀都會有相應的開銷。同時當系統存在大量線程時,系統會通過時間片輪轉的方式調度每個線程,因此線程不可能做到絕對的并發,除非線程數量小于等于CPU的核心數。

主線程和子線程

主線程主要處理界面交互邏輯,由于用戶隨時會和界面交互,所以主線程在任何時候都需要有較高響應速度,則不能執行耗時的任務。

Android3.0開始,網絡訪問將會失敗并拋出NetworkOnMainThreadException這個異常,這樣做是為了避免主線程由于被耗時操作所阻塞從而現ANR現象。

Android中的線程形態

AsyncTask

AsyncTask是一種輕量級的異步任務類, 他可以在線程池中執行后臺任務, 然后把執行的進度和最終的結果傳遞給主線程并在主線程更新UI. 從實現上來說. AsyncTask封裝了Thread和Handler, 通過AsyncTask可以更加方便地執行后臺任務,但是AsyncTask并不適合進行特別耗時的后臺任務,對于特別耗時的任務來說, 建議使用線程池。

AsyncTask就是一個抽象的泛型類. 這三個泛型的意義:

  1. Params:參數的類型
  2. Progress:后臺任務的執行進度的類型
  3. Result:后臺任務的返回結果的類型

如果不需要傳遞具體的參數, 那么這三個泛型參數可以用Void來代替。

四個方法:

  • onPreExecute()
    在主線程執行,在異步任務執行之前,此方法會被調用,一般可以用于做一些準備工作。
  • doInBackground()
    在線程池中執行,此方法用于執行異步任務,參數params表示異步任務的輸入參數。在此方法中可以通過publishProgress()方法來更新任務的進度,publishProgress()方法會調用onProgressUpdate()方法,另外此方法需要返回計算結果給onPostExecute()。
  • onProgressUpdate()
    在主線程執行,在異步任務執行之后,此方法會被調用,其中result參數是后臺任務的返回值,即doInBackground()的返回值。
  • onPostExecute()
    在主線程執行,在異步任務執行之后,此方法會被調用,其中result參數是后臺任務的返回值,即doInBackground的返回值。

除了上述的四種方法,還有onCancelled(),它同樣在主線程執行,當異步任務被取消時,onCancelled()方法會被調用,這個時候onPostExecute()則不會被調用。

AsyncTask在使用過程中有一些條件限制:

  • AsyncTask的類必須在主線程被加載,這就意味著第一次訪問AsyncTask必須發生在主線程,這個問題不是絕對,因為在Android 4.1及以上的版本已經被系統自動完成。在Android 5.0的源碼中,可以看到ActivityThread#main()會調用AsyncTask#init()方法。
  • AsyncTask的對象必須在主線程中創建。
  • execute方法必須在UI線程調用。
  • 不要在程序中直接調用onPreExecute(),onPostExecute(),doInBackground和onProgressUpdate()。
  • 一個AsyncTask對象只能執行一次,即只能調用一次execute()方法,否則會報運行時異常。
  • 在Android 1.6之前,AsyncTask是串行執行任務的;Android 1.6的時候AsyncTask開始采用線程池里處理并行任務;但是Android 3.0開始,為了避免AsyncTask帶來的并發錯誤,AsyncTask又采用了一個線程來串行的執行任務。盡管如此在3.0以后,仍然可以通過AsyncTask#executeOnExecutor()方法來并行執行任務。
AsyncTask的工作原理

AsyncTask中有兩個線程池(SerialExecutor和THREAD_POOL_EXECUTOR)和一個Handler(InternalHandler),其中線程池SerialExecutor用于任務的排列,而線程池THREAD_POOL_EXECUTOR用于真正的執行任務,而InternalHandler用于將執行環境從線程切換到主線程,其本質仍然是線程的調用過程。

AsyncTask的排隊過程:首先系統會把AsyncTask#Params參數封裝成FutureTask對象,FutureTask是一個并發類,在這里充當了Runnable的作用。接著這個FutureTask會交給SerialExecutor#execute()方法去處理。這個方法首先會把FutureTask對象插入到任務隊列mTasks中,如果這個時候沒有正在活動AsyncTask任務,那么就會調用SerialExecutor#scheduleNext()方法來執行下一個AsyncTask任務。同時當一個AsyncTask任務執行完后,AsyncTask會繼續執行其他任務直到所有的任務都執行完畢為止,從這一點可以看出,在默認情況下,AsyncTask是串行執行的。

HandlerThread

HandlerThread繼承了Thread,它是一種可以使用Handler的Thread,它的實現也很簡單,就是run方法中通過Looper.prepare()來創建消息隊列,并通過Looper.loop()來開啟消息循環,這樣在實際的使用中就允許在HandlerThread中創建Handler。

從HandlerThread的實現來看,它和普通的Thread有顯著的不同之處。普通的Thread主要用于在run方法中執行一個耗時任務;而HandlerThread在內部創建了消息隊列,外界需要通過Handler的消息方式來通知HandlerThread執行一個具體的任務。HandlerThread是一個很有用的類,在Android中一個具體使用場景就是IntentService。

由于HandlerThread#run()是一個無線循環方法,因此當明確不需要再使用HandlerThread時,最好通過quit()或者quitSafely()方法來終止線程的執行。

IntentService

IntentSercie是一種特殊的Service,繼承了Service并且是抽象類,任務執行完成后會自動停止,優先級遠高于普通線程,適合執行一些高優先級的后臺任務;IntentService封裝了HandlerThread和Handler

  1. onCreate方法自動創建一個HandlerThread
  2. 用它的Looper構造了一個Handler對象mServiceHandler,這樣通過mServiceHandler發送的消息都會在HandlerThread執行。
  3. IntentServiced的onHandlerIntent方法是一個抽象方法,需要在子類實現,onHandlerIntent方法執行后,stopSelt(int startId)就會停止服務,如果存在多個后臺任務,執行完最后一個stopSelf(int startId)才會停止服務。

Android線程池

優點:

  • 重用線程池的線程,減少線程創建和銷毀帶來的性能開銷。
  • 控制線程池的最大并發數,避免大量線程互相搶系統資源導致阻塞。
  • 提供定時執行和間隔循環執行功能。

Android中的線程池的概念來源于Java中的Executor,Executor是一個接口,真正的線程池的實現為ThreadPoolExecutor.Android的線程池大部分都是通過Executor提供的工廠方法創建的。ThreadPoolExecutor提供了一系列參數來配制線程池,通過不同的參數可以創建不同的線程池。而從功能的特性來分的話可以分成四類。

ThreadPoolExecutor

ThreadPoolExecutor是線程池的真正實現,它的構造方法提供了一系列參數來配置線程池,這些參數將會直接影響到線程池的功能特性。

public ThreadPoolExecutor(int corePoolSize,
                 int maximumPoolSize,
                 long keepAliveTime,
                 TimeUnit unit,
                 BlockingQueue<Runnable> workQueue) {
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
    Executors.defaultThreadFactory(), defaultHandler);
}
  • corePoolSize:線程池的核心線程數,默認情況下,核心線程會在線程池中一直存活,即使都處于閑置狀態。如果將ThreadPoolExecutor#allowCoreThreadTimeOut屬性設置為true,那么閑置的核心線程在等待新任務到來時會有超時的策略,這個時間間隔由keepAliveTime屬性來決定。當等待時間超過了keepAliveTime設定的值那么核心線程將會終止。
  • maximumPoolSize:線程池所能容納的最大線程數,當活動線程數達到這個數值之后,后續的任務將會被阻塞。
  • keepAliveTime:非核心線程閑置的超時時長,超過這個時長,非核心線程就會被回收。allowCoreThreadTimeOut這個屬性為true的時候,這個屬性同樣會作用于核心線程。
  • unit:用于指定keepAliveTime參數的時間單位,這是一個枚舉,常用的有TimeUtil.MILLISECONDS(毫秒),TimeUtil.SECONDS(秒)以及TimeUtil.MINUTES(分)
  • workQueue:線程池中的任務隊列,通過線程池的execute方法提交的Runnable對象會存儲在這個參數中。
  • threadFactory:線程工廠,為線程池提供創建新線程的功能。ThreadFactory是一個接口。

ThreadPoolExecutor執行任務大致遵循如下規則:

  1. 如果線程池中的線程數量未達到核心線程的數量,那么會直接啟動一個核心線程來執行任務。
  2. 如果線程池中的線程數量已經達到或者超過核心線程的數量,那么任務會被插入到任務隊列中排隊等待執行。
  3. 如果在步驟2中無法將任務插入到任務隊列中,這通常是因為任務隊列已滿,這個時候如果線程數量未達到線程池的規定的最大值,那么會立刻啟動一個非核心線程來執行任務。
  4. 如果步驟3中的線程數量已經達到最大值的時候,那么會拒絕執行此任務,ThreadPoolExecutor會調用RejectedExecution方法來通知調用者。

AsyncTask的THREAD_POOL_EXECUTOR線程池配置:

  • 核心線程數等于CPU核心數+1。
  • 線程池最大線程數為CPU核心數的2倍+1。
  • 核心線程無超時機制,非核心線程的閑置超時時間為1秒。
  • 任務隊列容量是128。
線程池的分類
  • FixedThreadPool
    通過Executor#newFixedThreadPool()方法來創建. 它是一種線程數量固定的線程池, 當線程處于空閑狀態時, 它們并不會被回收, 除非線程池關閉了. 當所有的線程都處于活動狀態時, 新任務都會處于等待狀態, 直到有線程空閑出來. 由于FixedThreadPool只有核心線程并且這些核心線程不會被回收, 這意味著它能夠更加快速地響應外界的請求.

  • CachedThreadPool
    通過Executor#newCachedThreadPool()方法來創建. 它是一種線程數量不定的線程池, 它只有非核心線程, 并且其最大值線程數為Integer.MAX_VALUE. 這就可以認為這個最大線程數為任意大了. 當線程池中的線程都處于活動的時候, 線程池會創建新的線程來處理新任務, 否則就會利用空閑的線程來處理新任務. 線程池中的空閑線程都有超時機制, 這個超時時長為60S, 超過這個時間那么空閑線程就會被回收。
    和FixedThreadPool不同的是, CachedThreadPool的任務隊列其實相當于一個空集合, 這將導致任何任務都會立即被執行, 因為在這種場景下SynchronousQueue是無法插入任務的. SynchronousQueue是一個非常特殊的隊列, 在很多情況下可以把它簡單理解為一個無法存儲元素的隊列. 在實際使用中很少使用.這類線程比較適合執行大量的耗時較少的任務。

  • ScheduledThreadPool
    通過Executor#newScheduledThreadPool()方法來創建. 它的核心線程數量是固定的, 而非核心線程數是沒有限制的, 并且當非核心線程閑置時會立刻被回收掉. 這類線程池用于執行定時任務和具有固定周期的重復任務。

  • SingleThreadExecutor
    通過Executor#newSingleThreadPool()方法來創建. 這類線程池內部只有一個核心線程, 它確保所有的任務都在同一個線程中按順序執行. 這類線程池意義在于統一所有的外界任務到一個線程中, 這使得在這些任務之間不需要處理線程同步的問題。

更多內容戳這里(整理好的各種文集)

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 因為用心,才會讓平淡無奇的日子變得可愛,讓庸常的生活不那么乏味。 2007年10月6日 ...
    殘陽物語閱讀 1,320評論 4 6
  • 《千鐘醉》 四季如歌 文 / 文君 一鐘醉,竹林溪水煙波媚,山影云亭日夕暉。清芬裊裊,幽蘭淡淡,燕兒枝間戲。 二鐘...
    君無情545閱讀 327評論 0 2
  • 率土記錄哪家強?簡書作者粉仔強! 提示:這個游戲跟我沒任何關系,我只是普通玩家、普通消費者,而且我的觀點一直都是少...
    粉仔強閱讀 627評論 0 0
  • 封少艷子閱讀 284評論 0 1