通過前幾篇文章的學習,你們對多線程應該有些瞭解了吧,這裏附上前三篇文章的連接,尚未看過的小夥伴快去複習吧~~java
多線程基礎篇入門git
線程的生命週期和經常使用 APIsgithub
生產者消費者問題面試
那相信你們也能感覺到,其實用多線程是很麻煩的,包括線程的建立、銷燬和調度等等,並且咱們平時工做時好像也並無這樣來 new 一個線程,實際上是由於不少框架的底層都用到了線程池。數據庫
線程池是幫助咱們管理線程的工具,它維護了多個線程,能夠下降資源的消耗,提升系統的性能。多線程
而且經過使用線程池,咱們開發人員能夠更好的把精力放在任務代碼上,而不去管線程是如何執行的,實現任務提交和執行的解藕。框架
本文將從是何、爲什麼、如何的角度來說解線程池:less
線程池是一種池化的技術,相似的還有數據庫鏈接池、HTTP 鏈接池等等。async
池化的思想主要是爲了減小每次獲取和結束資源的消耗,提升對資源的利用率。工具
好比在一些偏遠地區打水不方便的,你們會每段時間把水打過來存在池子裏,這樣平時用的時候就直接來取就行了。
線程池同理,正是由於每次建立、銷燬線程須要佔用太多系統資源,因此咱們建這麼一個池子來統一管理線程。用的時候從池子裏拿,不用了就放回來,也不用你銷燬,是否是方便了不少?
Java 中的線程池是由 juc
即 java.util.concurrent
包來實現的,最主要的就是 ThreadPoolExecutor
這個類。具體怎麼用咱們下文再說。
在多線程的第一篇文章中咱們說過,進程會申請資源,拿來給線程用,因此線程是很佔用系統資源的,那麼咱們用線程池來統一管理線程就可以很好的解決這種資源管理問題。
好比由於不須要建立、銷燬線程,每次須要用的時候我就去拿,用完了以後再放回去,因此節省了不少資源開銷,能夠提升系統的運行速度。
而統一的管理和調度,能夠合理分配內部資源,根據系統的當前狀況調整線程的數量。
那總結來講有如下 3 個好處:
說了這麼多,終於到了今天的重點,咱們來看下究竟怎麼用線程池吧~
Java 給咱們提供了 Executor
接口來使用線程池。
咱們經常使用的線程池有這兩大類:
它倆的區別呢,就是第一個是普通的,第二個是能夠定時執行的。
固然還有其餘線程池,好比 JDK 1.7 纔出現的 ForkJoinPool
,能夠把大任務分割成小任務來執行,最後再大一統。
那麼任務提交到一個線程池以後,它會經歷一個怎樣的過程呢?
線程池在內部實際上採用了生產者消費者模型(還不清楚這個模型的在文章開頭有改文章的連接)將線程和任務解藕,從而使線程池同時管理任務和線程。
當任務提交到線程池裏以後,須要通過如下流程:
BlockingQueue
,在生產者消費者這節裏提到過。咱們主要說下 ThreadPoolExecutor
,它是最經常使用的線程池。
這裏咱們能夠看到,這個類裏有 4 個構造方法,點進去仔細看,其實前三個都 call 了最後一個,因此咱們只須要看最後一個就好。
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { ... }
這裏咱們來仔細看下這幾個參數:
corePoolSize the number of threads to keep in the pool, even if they are idle, unless { \@code allowCoreThreadTimeOut} is set
maximumPoolSize the maximum number of threads to allow in the pool
keepAliveTime when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.
unit the time unit for the { \@code keepAliveTime} argument
workQueue the queue to use for holding tasks before they are executed.
threadFactory the factory to use when the executor creates a new thread
handler the handler to use when execution is blocked because the thread bounds and queue capacities are reached
因此咱們能夠經過本身傳入這 7 個參數構造線程池,固然了,貼心的 Java 也給咱們包裝好了幾類線程池能夠很方便的拿來使用。
咱們具體來看每一個的含義和用法。
public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
這裏咱們能夠看到,
Integer.MAX_VALUE
;它的適用場景在源碼裏有說:
These pools will typically improve the performance of programs that execute many short-lived asynchronous tasks.
來看怎麼用:
public class newCacheThreadPool { public static void main(String[] args) { // 建立一個線程池 ExecutorService executorService = Executors.newCachedThreadPool(); // 向線程池提交任務 for (int i = 0; i < 50; i++) { executorService.execute(new Task());//線程池執行任務 } executorService.shutdown(); } }
執行結果:
能夠很清楚的看到,線程 一、二、三、五、6 都很快重用了。
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
這個線程池的特色是:
它的適用場景是:
Creates a thread pool that reuses a fixed number of threads operating off a shared unbounded queue.
public class FixedThreadPool { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(10); for (int i = 0; i < 200; i++) { executorService.execute(new Task()); } executorService.shutdown(); } }
這裏我限制了線程池裏最多有 10 個線程,哪怕有 200 個任務須要執行,也只有 1-10 這 10 個線程能夠運行。
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
這個線程池顧名思義,裏面只有 1 個線程。
適用場景是:
Creates an Executor that uses a single worker thread operating off an unbounded queue.
咱們來看下效果。
public class SingleThreadPool { public static void main(String[] args) { ExecutorService executorService = Executors.newSingleThreadExecutor(); for (int i = 0; i < 100; i++) { executorService.execute(new Task()); } executorService.shutdown(); } }
這裏在出結果的時候我可以明顯的感受到有些卡頓,這在前兩個例子裏是沒有的,畢竟這裏只有一個線程在運行嘛。
因此在使用線程池時,其實都是調用的 ThreadPoolExecutor
這個類,只不過傳遞的不一樣參數。
這裏要特別注意兩個參數:
workQueue
的選擇,這個就是阻塞隊列的選擇,若是要說還得這麼一大篇文章,以後有機會再寫吧。handler
的設置。那咱們發現,在上面的 3 個具體線程池裏,其實都沒有設定 handler
,這是由於它們都使用了 defaultHandler
。
/** * The default rejected execution handler */ private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();
在 ThreadPoolExecutor
裏有 4 種拒絕策略,這 4 種策略都是 implements
了 RejectedExecutionHandler
:
AbortPolicy
表示拒絕任務並拋出一個異常 RejectedExecutionException
。這個我稱之爲「正式拒絕」,好比你面完了最後一輪面試,最終接到 HR 的拒信。DiscardPolicy
拒絕任務但不吭聲。這個就是「默拒」,好比大部分公司拒簡歷的時候都是默拒。DiscardOldestPolicy
顧名思義,就是把老的任務丟掉,執行新任務。CallerRunsPolicy
直接調用線程處理該任務,就是 VIP 嘛。因此這 3 種線程池都使用的默認策略也就是第一種,光明正大的拒絕。
好了以上就是本文的全部內容了。固然線程池還有不少知識點,好比 execute()
和 submit()
方法,線程池的生命週期等等。
但隨着閱讀量的逐漸走低,齊姐意識到了這彷佛有什麼誤會,因此這篇文章是多線程系列的最後一篇了。
本文已收錄至個人 Github 上:https://github.com/xiaoqi6666/NYCSDE
,點擊閱讀原文直達,這個 Github 彙總了我全部的文章和資料,以後也會一直更新和維護,還但願你們幫忙點個 Star
,大家的支持和承認,就是我創做的最大動力!
我是小齊,終生學習者,天天晚上 9 點,雲自習室裏不見不散!