導語
在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就是一個抽象的泛型類. 這三個泛型的意義:
- Params:參數的類型
- Progress:后臺任務的執行進度的類型
- 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
- onCreate方法自動創建一個HandlerThread
- 用它的Looper構造了一個Handler對象mServiceHandler,這樣通過mServiceHandler發送的消息都會在HandlerThread執行。
- 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執行任務大致遵循如下規則:
- 如果線程池中的線程數量未達到核心線程的數量,那么會直接啟動一個核心線程來執行任務。
- 如果線程池中的線程數量已經達到或者超過核心線程的數量,那么任務會被插入到任務隊列中排隊等待執行。
- 如果在步驟2中無法將任務插入到任務隊列中,這通常是因為任務隊列已滿,這個時候如果線程數量未達到線程池的規定的最大值,那么會立刻啟動一個非核心線程來執行任務。
- 如果步驟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()方法來創建. 這類線程池內部只有一個核心線程, 它確保所有的任務都在同一個線程中按順序執行. 這類線程池意義在于統一所有的外界任務到一個線程中, 這使得在這些任務之間不需要處理線程同步的問題。