Android讀書筆記(11)—— Android的線程和線程池

前言

在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()方法來創建. 這類線程池內部只有一個核心線程, 它確保所有的任務都在同一個線程中按順序執行. 這類線程池意義在于統一所有的外界任務到一個線程中, 這使得在這些任務之間不需要處理線程同步的問題

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容

  • Android中的線程 線程,在Android中是非常重要的,主線程處理UI界面,子線程處理耗時操作。如果在主線程...
    shenhuniurou閱讀 766評論 0 3
  • 從用途上來說,線程分為主線程和子線程,主線程主要處理和界面相關的事情,子線程則往往用于執行耗時操作。 除了Thre...
    小柏不是大白閱讀 642評論 0 3
  • 主席(任玉剛)當時在他群里發這書簡介的時候,只看目錄就知道是我想要的,哈哈,書還是有些難度的,不過真是一本超棒的書...
    HuDP閱讀 5,762評論 14 69
  • 這是致前任的一篇日記 提起前任,大家應該都不會陌生,前任對你來說是什么呢?是一段記憶,還是一段不可觸碰的傷痛。對你...
    愛笑de小美閱讀 264評論 0 1
  • 不知道是我的問題還是誰的問題,我覺得我身邊的他們都是病孩子,也包括我。 有時候也不知道我在思考什么,渾渾噩噩一整天...
    李六六六閱讀 385評論 0 0