線程池的作用
-
減少資源的開銷
減少了每次創建線程、銷燬線程的開銷。 -
提高響應速度
每次請求到來時,由於線程的創建已經完成,故可以直接執行任務,因此提高了響應速度。 -
提高線程的可管理性
線程是一種稀缺資源,若不加以限制,不僅會佔用大量資源,而且會影響系統的穩定性。
因此,線程池可以對線程的創建與停止、線程數量等等因素加以控制,使得線程在一種可控的範圍內運行,不僅能保證系統穩定運行,而且方便性能調優。
線程池的實現原理
線程池一般由兩種角色構成:多個工作線程 和 一個阻塞隊列。
-
工作線程
工作線程是一組已經處在運行中的線程,它們不斷地向阻塞隊列中領取任務執行。 -
阻塞隊列
阻塞隊列用於存儲工作線程來不及處理的任務。當工作線程都在執行任務時,到來的新任務就只能暫時在阻塞隊列中存儲。
ThreadPoolExecutor的使用
創建線程池
通過如下代碼即可創建一個線程池:
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, timeUnit, runnableTaskQueue, handler);
- 1
-
corePoolSize:基本線程數量
它表示你希望線程池達到的一個值。線程池會盡量把實際線程數量保持在這個值上下。 -
maximumPoolSize:最大線程數量
這是線程數量的上界。
如果實際線程數量達到這個值:- 阻塞隊列未滿:任務存入阻塞隊列等待執行
- 阻塞隊列已滿:調用飽和策略
-
keepAliveTime:空閒線程的存活時間
當實際線程數量超過corePoolSize時,若線程空閒的時間超過該值,就會被停止。
PS:當任務很多,且任務執行時間很短的情況下,可以將該值調大,提高線程利用率。 -
timeUnit:keepAliveTime的單位
- runnableTaskQueue:任務隊列
這是一個存放任務的阻塞隊列,可以有如下幾種選擇:- ArrayBlockingQueue
它是一個由數組實現的阻塞隊列,FIFO。 - LinkedBlockingQueue
它是一個由鏈表實現的阻塞隊列,FIFO。
吞吐量通常要高於ArrayBlockingQueue。
fixedThreadPool使用的阻塞隊列就是它。
它是一個無界隊列。 - SynchronousQueue
它是一個沒有存儲空間的阻塞隊列,任務提交給它之後必須要交給一條工作線程處理;如果當前沒有空閒的工作線程,則立即創建一條新的工作線程。
cachedThreadPool用的阻塞隊列就是它。
它是一個無界隊列。 - PriorityBlockingQueue
它是一個優先權阻塞隊列。
- ArrayBlockingQueue
- handler:飽和策略
當實際線程數達到maximumPoolSize,並且阻塞隊列已滿時,就會調用飽和策略。
JDK1.5由四種飽和策略:- AbortPolicy
默認。直接拋異常。 - CallerRunsPolicy
只用調用者所在的線程執行任務。 - DiscardOldestPolicy
丟棄任務隊列中最久的任務。 - DiscardPolicy
丟棄當前任務。
- AbortPolicy
提交任務
可以向ThreadPoolExecutor提交兩種任務:Callable和Runnable。
-
Callable
該類任務有返回結果,可以拋出異常。
通過submit函數提交,返回Future對象。
可通過get獲取執行結果。 -
Runnable
該類任務只執行,無法獲取返回結果,並在執行過程中無法拋異常。
通過execute提交。
關閉線程池
關閉線程池有兩種方式:shutdown和shutdownNow,關閉時,會遍歷所有的線程,調用它們的interrupt函數中斷線程。但這兩種方式對於正在執行的線程處理方式不同。
- shutdown()
僅停止阻塞隊列中等待的線程,那些正在執行的線程就會讓他們執行結束。 - shutdownNow()
不僅會停止阻塞隊列中的線程,而且會停止正在執行的線程。
ThreadPoolExecutor運行機制
當有請求到來時:
- 若當前實際線程數量 少於 corePoolSize,即使有空閒線程,也會創建一個新的工作線程;
- 若當前實際線程數量處於corePoolSize和maximumPoolSize之間,並且阻塞隊列沒滿,則任務將被放入阻塞隊列中等待執行;
- 若當前實際線程數量 小於 maximumPoolSize,但阻塞隊列已滿,則直接創建新線程處理任務;
- 若當前實際線程數量已經達到maximumPoolSize,並且阻塞隊列已滿,則使用飽和策略。
設置合理的線程池大小
任務一般可分爲:CPU密集型、IO密集型、混合型,對於不同類型的任務需要分配不同大小的線程池。
-
CPU密集型任務
儘量使用較小的線程池,一般爲CPU核心數+1。
因爲CPU密集型任務使得CPU使用率很高,若開過多的線程數,只能增加上下文切換的次數,因此會帶來額外的開銷。 -
IO密集型任務
可以使用稍大的線程池,一般爲2*CPU核心數。
IO密集型任務CPU使用率並不高,因此可以讓CPU在等待IO的時候去處理別的任務,充分利用CPU時間。 -
混合型任務
可以將任務分成IO密集型和CPU密集型任務,然後分別用不同的線程池去處理。
只要分完之後兩個任務的執行時間相差不大,那麼就會比串行執行來的高效。
因爲如果劃分之後兩個任務執行時間相差甚遠,那麼先執行完的任務就要等後執行完的任務,最終的時間仍然取決於後執行完的任務,而且還要加上任務拆分與合併的開銷,得不償失。