前言
在Android中,線程的形態有很多種:
- AsyncTask封裝了線程池和Handler,主要為了方便開發者在子線程中更新UI,底層是線程池。
- HandlerThread是具有消息循環的線程,內部可以使用handler,底層是Thread。
- IntentService是一種Service,內部采用HandlerThread來執行任務,當任務執行完畢后IntentService會自動退出。由于它是一種Service,所以不容易被系統殺死,底層是Thread 。
操作系統中,線程是操作系統調度的最小單元,同時線程又是一種受限的系統資源(不可能無限產生),其創建和銷毀都會有相應的開銷。同時當系統存在大量線程時,系統會通過時間片輪轉的方式調度每個線程,因此線程不可能做到絕對的并發,除非線程數量小于等于CPU的核心數。頻繁創建銷毀線程不明智,使用線程池是正確的做法。線程池會緩存一定數量的線程,通過線程池就可以避免因為頻繁創建和銷毀線程所帶來的系統開銷。
一、主線程和子線程
主線程也叫UI線程,作用是運行四大組件以及處理它們和用戶交互。子線程的作用是執行耗時操作,比如I/O,網絡請求等。從Android 3.0開始,主線程中訪問網絡將拋出異常。
二、Android中的線程形態
1、AsyncTask
AsyncTask是一種輕量級的異步任務類,封裝了Thread和Handler,可以在線程池中執行后臺任務,然后把執行的進度和最終的結果傳遞給主線程并更新UI。但并不適合進行特別耗時的后臺任務,對于特別耗時的任務來說, 建議使用線程池。
abstract class AsyncTask<Params, Progress, Result>
- Params:入參類型
- Progress:后臺任務的執行進度的類型
- Result:后臺任務的返回結果的類型
如果不需要傳遞具體的參數, 那么這三個泛型參數可以用Void來代替。
1.1、四個核心方法
-
onPreExecute() void
在主線程執行, 在異步任務執行之前, 此方法會被調用, 一般可以用于做一些準備工作。 -
doInBackground(Params... params) Result
在線程池中執行, 此方法用于執行異步任務, 參數params表示異步任務的輸入參數。 在此方法中可以通過publishProgress(Progress... values) void
方法來更新任務的進度,publishProgress()
方法會調用onProgressUpdate()
方法。另外此方法需要返回計算結果給onPostExecute()
-
onProgressUpdate(Progress... values) void
在主線程執行,當后臺任務publishProgress()
時,會被調用。 -
onPostExecute(Result res) void
在主線程執行, 在異步任務執行之后, 此方法會被調用, 其中result參數是后臺任務的返回值, 即doInBackground的返回值。
除了上述的四種方法,還有onCancelled()
, 它同樣在主線程執行, 當異步任務被取消時調用,這個時候onPostExecute()則不會被調用.
1.2、AsyncTask使用過程中的一些條件限制
- AsyncTask的類必須在主線程被加載, 這就意味著第一次訪問AsyncTask必須發生在主線程。在Android 4.1及以上的版本已經被系統自動完成。
- AsyncTask的對象必須在主線程中創建。
- execute方法必須在UI線程調用。
- 不要在程序中直接調用onPreExecute(), onPostExecute(), doInBackground和onProgressUpdate()
- 一個AsyncTask對象只能執行一次, 即只能調用一次execute()方法, 否則會報運行時異常。
- AsyncTask采用了一個線程來串行的執行任務。 盡管如此在3.0以后, 仍然可以通過
AsyncTask#executeOnExecutor()
方法來并行執行任務。
1.3、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是串行執行的。
2、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()方法來終止線程的執行.
3、IntentService
IntentSercie是一種特殊的Service,繼承了Service并且是抽象類,任務執行完成后會自動停止,優先級遠高于普通線程,適合執行一些高優先級的后臺任務; IntentService封裝了HandlerThread和Handler
onCreate方法自動創建一個HandlerThread,用它的Looper構造了一個Handler對象mServiceHandler,這樣通過mServiceHandler發送的消息都會在HandlerThread執行;IntentServiced的onHandlerIntent方法是一個抽象方法,需要在子類實現,onHandlerIntent方法執行后,stopSelt(int startId)就會停止服務,如果存在多個后臺任務,執行完最后一個stopSelf(int startId)才會停止服務。
三、Android線程池
線程池優點:
1) 重用線程池的線程,減少線程創建和銷毀帶來的性能開銷
2) 控制線程池的最大并發數,避免大量線程互相搶系統資源導致阻塞
3) 提供定時執行和間隔循環執行功能
Android中的線程池的概念來源于Java中的Executor,Executor是一個接口, 真正的線程池的實現為ThreadPoolExecutor。Android的線程池大部分都是通過Executor提供的工廠方法創建的。 ThreadPoolExecutor提供了一系列參數來配制線程池,通過不同的參數可以創建不同的線程池。 而從功能的特性來分的話可以分成四類。
1、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
2、線程池的分類
FixedThreadPool
通過Executor#newFixedThreadPool()方法來創建. 它是一種線程數量固定的線程池, 當線程處于空閑狀態時, 它們并不會被回收, 除非線程池關閉了. 當所有的線程都處于活動狀態時, 新任務都會處于等待狀態, 直到有線程空閑出來. 由于FixedThreadPool只有核心線程并且這些核心線程不會被回收, 這意味著它能夠更加快速地響應外界的請求.CachedThreadPool
通過Executor#newCachedThreadPool()方法來創建. 它是一種線程數量不定的線程池, 它只有非核心線程, 并且其最大值線程數為Integer.MAX_VALUE. 這就可以認為這個最大線程數為任意大了. 當線程池中的線程都處于活動的時候, 線程池會創建新的線程來處理新任務, 否則就會利用空閑的線程來處理新任務. 線程池中的空閑線程都有超時機制, 這個超時時長為60S, 超過這個時間那么空閑線程就會被回收.
和FixedThreadPool不同的是, CachedThreadPool的任務隊列其實相當于一個空集合, 這將導致任何任務都會立即被執行, 因為在這種場景下SynchronousQueue是無法插入任務的. SynchronousQueue是一個非常特殊的隊列, 在很多情況下可以把它簡單理解為一個無法存儲元素的隊列. 在實際使用中很少使用.這類線程比較適合執行大量的耗時較少的任務ScheduledThreadPool
通過Executor#newScheduledThreadPool()方法來創建. 它的核心線程數量是固定的, 而非核心線程數是沒有限制的, 并且當非核心線程閑置時會立刻被回收掉. 這類線程池用于執行定時任務和具有固定周期的重復任務SingleThreadExecutor
通過Executor#newSingleThreadPool()方法來創建. 這類線程池內部只有一個核心線程, 它確保所有的任務都在同一個線程中按順序執行. 這類線程池意義在于統一所有的外界任務到一個線程中, 這使得在這些任務之間不需要處理線程同步的問題