Android 線程池全解析

什么是線程池

  • 我們都知道線程是什么,但是一提到線程池,給人的第一個感覺就是一堆線程,這樣的理解其實不太對,線程池可以只有一個線程,也可以有多個線程,而線程池最大的作用是管理和復用線程。一提到管理線程,我們都知道線程池可以幫我們創(chuàng)建線程,也可以幫我們銷毀線程。但是一提到復用線程,相信大多數(shù)人都愣了,要知道 Thread.start 執(zhí)行完 Runnable.run 方法之后線程就自動停止了,也就是說 Thread 對象只能調(diào)用一次 start 方法,那么在這種情況下線程池是如何復用線程的呢?

  • 都說源碼是最好的老師,接下來讓我們通過源碼來看看這葫蘆里面賣的什么藥

線程池的原理

  • 我們還是從線程池的寫法入手源碼
  • 在線程池的執(zhí)行方法中,我們發(fā)現(xiàn)了一個出現(xiàn)頻率很高的 API,接下來讓我們看看這句代碼干了什么事
  • 我們可以看到,線程池拿到任務之后丟給了 Worker 類處理
  • 進 Worker 類一看,不看不知道,一看嚇一跳,這個類還創(chuàng)建了 Thread 對象
  • 有一個非常重要細節(jié),就是創(chuàng)建線程的時候,傳入的并不是我們給線程池的那個 Runnable 對象,而是 Worker 對象本身,也就是說線程 start 的時候,Worker 類的 run 方法會被執(zhí)行
  • 在這里可以看到我們剛剛傳入 Runnable 對象,然后開啟了一個 while 循環(huán),循環(huán)的意思是:只要 task 對象不為空,那么就會一直調(diào)用 task = getTask(),直到獲取到的 task 對象為空了才會停止循環(huán)

  • 那么 getTask 方法里面到底干了什么,讓我們接著看

  • 通過這兩段代碼,我們可以得知,getTask 其實就是往阻塞隊列中取出 Runnable 對象
  • 通過這些,我們可以得出,線程池復用線程的原理,創(chuàng)建 Thread 對象的時候傳入的不是我們的 Runnable 對象,而是通過線程池自定義的 Runnable 類,這個類主要的作用不僅是執(zhí)行我們的 Runnable 對象,當我們傳入的任務被某個線程執(zhí)行完畢之后,它還會遍歷阻塞隊列中其他未執(zhí)行的任務,這樣就能達到一個線程執(zhí)行多個 Runnable 對象的效果,這個就是線程池復用線程的原理。

什么時候該用線程池

  • 通過源碼我們了解到了線程池的工作機制,那么問題來了,什么情況下該用線程池,什么情況下不該用線程池?

  • 這個問題其實很簡單,源碼已經(jīng)告訴我們答案了,當線程池中只有一個線程并且只執(zhí)行一次任務的時候,我們可以考慮不用線程池,直接創(chuàng)建 Thread 對象來執(zhí)行這個任務。

  • 也就是說線程池的生命周期只有單個任務的情況下,沒有任何優(yōu)勢可言,但是如果在多任務同時并發(fā)的情況下,線程池是可以幫我們減少線程數(shù)量的,用一句最簡單的話來理解就是,用最少的人力干完所有的活,人太少活太多不行,人太多活太少也不行。

線程池核心參數(shù)

  • 接下來讓我們講講創(chuàng)建線程池的幾個核心參數(shù)
  • corePoolSize 是核心線程數(shù),何為核心線程數(shù)?源碼注釋已經(jīng)寫得很明白了,也就是最小線程數(shù),規(guī)定線程池里面最少必須有幾個線程在工作,這些核心線程在沒有任務可以執(zhí)行的時候還必須存活著,除非我們設定了核心線程的存活時間,否則這些核心線程永遠不會停止工作。
  • maximumPoolSize 是最大線程數(shù),這里面不僅包含了核心線程數(shù),還包含了非核心線程數(shù),那么問題來了,何為非核心線程?

  • 這不得不來場比較了,核心線程和非核心線程最大的區(qū)別是:核心線程在沒有任務的情況下不會被回收,而非核心線程一旦沒有了任務就會被回收。

  • 舉一個生活中最常見的例子,我們?nèi)绻押诵木€程比作一個正式工,那么非核心線程就是一個外包工。正式工沒活干沒事,但如果外包工沒活干了的話是要面臨被裁員的。

  • workQueue 是阻塞隊列,為什么要用隊列(Queue),因為隊列是先進先出,先進來的任務先取出,最終先進來的任務最先執(zhí)行完畢的可能性就大,但這還得考慮任務具體的耗時情況而定,在耗時相同的情況下,先進來的任務就先執(zhí)行完畢。當然這個隊列還有其他用處,那就是存放一些未執(zhí)行的任務,具體有什么作用,可以讓我們來一場實驗。

  • 實驗場景:核心線程數(shù) = 1 ,最大線程數(shù) = 3,開啟 for 循環(huán)執(zhí)行 10 個任務,任務內(nèi)容:sleep 1 秒并打印當前線程名

    • 當阻塞隊列容量無限大時,10 個任務只出現(xiàn) 1 個線程在排隊執(zhí)行

    • 當阻塞隊列容量設置為 10 個或者 9 個時,10 個任務也是只出現(xiàn) 1 個線程在排隊執(zhí)行

    • 當阻塞隊列容量設置為 8 個時,10 個任務出現(xiàn)了 2 個線程在并發(fā)執(zhí)行

    • 當阻塞隊列容量設置為 7 個時,10 個任務出現(xiàn)了 3 個線程在并發(fā)執(zhí)行

    • 當阻塞隊列容量設置為 6 個時,線程池拋出異常,表示拒絕執(zhí)行任務

  • 實驗結(jié)論:往線程池添加一個新的任務時,如果核心線程處于空閑狀態(tài),任務會直接交由核心線程處理,否則任務會存放到阻塞隊列中,當阻塞隊列中的任務數(shù)量超過設定的最大值時,才會開啟非核心線程去執(zhí)行,如果當前任務總量 > 阻塞隊列的最大容量 + 最大線程數(shù)時,線程池則會拒絕執(zhí)行該任務。

  • keepAliveTime 是非核心線程的存活時間,當線程池中的非核心線程沒有任務執(zhí)行的時候,如果超過了指定的時間還是沒有執(zhí)行任何任務的時候,那么這個非核心線程會在超時后被回收掉,如果我們不指定這個時間,那么這些非核心線程將永遠不會被回收。

  • 其他參數(shù)不是那么重要,這里直接略過不講,接下來簡單介紹一下系統(tǒng) API 給我們提供的四種線程池。

系統(tǒng)提供的四種線程池

  • 這里創(chuàng)建了一個核心線程數(shù)和最大線程數(shù)都為 1 的線程池,簡單理解這個線程池只有一個線程,正如它的方法名一樣(new Single Thread Executor)
  • 這個線程池跟上一個線程池非常像,核心線程數(shù)和最大線程數(shù)都是用同一個數(shù)值,只不過上一個線程池是寫死的 1,而這個線程池可以自定義這個數(shù)值。
  • 這個線程池沒有核心線程數(shù),也沒有限制最大線程數(shù),那么可以得出這個線程池里面的線程都是非核心線程,并且還規(guī)定了非核心線程的存活時間不能超過 60 秒。
  • 最后一個線程池的特點是:核心線程數(shù)是固定的,但不限制最大線程數(shù),非核心線程的閑置時間不能超過 10 毫秒。

  • 了解過后才發(fā)現(xiàn),這四種線程池無非是核心線程數(shù)、最大線程數(shù)、非核心線程的存活時間這幾個參數(shù)的定義上徘徊

Android 技術討論 Q 群:10047167

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 線程池解析第一章-源碼解析線程池解析第二章-線程池源碼問題總結(jié) 線程池基本介紹 為什么要使用線程池 對于系統(tǒng)和服務...
    不改青銅本色閱讀 511評論 0 1
  • 最先學習線程的時候,我有些一帶而過,代碼倒是看懂了,但是問題也有不少,導致對很多細節(jié)不清楚,所以在這多做一些筆記,...
    文茶君閱讀 769評論 0 0
  • 概述 多線程技術主要解決處理器單元內(nèi)多個線程執(zhí)行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能...
    Tian_Peng閱讀 296評論 0 0
  • 什么是線程池 線程池實際上就是一個線程緩存集合,負責對線程進行統(tǒng)一分配、調(diào)優(yōu)和調(diào)度。線程是稀缺資源,它的創(chuàng)建與銷毀...
    九點半的馬拉閱讀 322評論 0 0
  • 久違的晴天,家長會。 家長大會開好到教室時,離放學已經(jīng)沒多少時間了。班主任說已經(jīng)安排了三個家長分享經(jīng)驗。 放學鈴聲...
    飄雪兒5閱讀 7,819評論 16 22

友情鏈接更多精彩內(nèi)容