java多線程學習(六)—— 線程池

什麼是線程池

Java中的線程池是運用場景最多的併發框架,幾乎所有需要異步或併發執行任務的程序都可以使用線程池。在開發過程中,合理地使用線程池能夠帶來3個好處。
第一:降低資源消耗。通過重複利用已創建的線程降低線程創建和銷燬造成的消耗。
第二:提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。
第三:提高線程的可管理性。線程是稀缺資源,如果無限制地創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一分配、調優和監控。但是,要做到合理利用線程池,必須對其實現原理了如指掌。

線程池的分類

Java是天生就支持併發的語言,支持併發意味着多線程,線程的頻繁創建在高併發及大數據量是非常消耗資源的,因爲java提供了線程池。在jdk1.5以前的版本中,線程池的使用是及其簡陋的,但是在JDK1.5後,有了很大的改善。JDK1.5之後加入了java.util.concurrent包,java.util.concurrent包的加入給予開發人員開發併發程序以及解決併發問題很大的幫助。這篇文章主要介紹下併發包下的Executor接口,Executor接口雖然作爲一個非常舊的接口(JDK1.5 2004年發佈),但是很多程序員對於其中的一些原理還是不熟悉,因此寫這篇文章來介紹下Executor接口,同時鞏固下自己的知識。如果文章中有出現錯誤,歡迎大家指出。
Executor框架的最頂層實現是ThreadPoolExecutor類,Executors工廠類中提供的newScheduledThreadPoolnewFixedThreadPoolnewCachedThreadPool方法其實也只是ThreadPoolExecutor的構造函數參數不同而已。通過傳入不同的參數,就可以構造出適用於不同應用場景下的線程池
通過 Executor 來創建四種線程的源碼分析,四種線程都是通過 ThreadPoolExecutor 類的構造方法來進行創建的,同樣,我們也可以通過 ThreadPoolExecutor 類來自定義線程池,下面簡單分析一下 ThreadPoolExecutor 類的構造方法的參數:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)

corePoolSize:核心線程池的大小,在線程池被創建之後,其實裏面是沒有線程的。(當然,調用prestartAllCoreThreads()或者prestartCoreThread()方法會預創建線程,而不用等着任務的到來)。當有任務進來的時候,纔會創建線程。當線程池中的線程數量達到corePoolSize之後,就把任務放到 緩存隊列當中。(就是 workQueue)。

maximumPoolSize:最大線程數量是多少。它標誌着這個線程池的最大線程數量。如果沒有最大數量,當創建的線程數量達到了 某個極限值,到最後內存肯定就爆掉了。

keepAliveTime:當線程沒有任務時,最多保持的時間,超過這個時間就被終止了。默認情況下,只有 線程池中線程數量 大於 corePoolSize時,keepAliveTime值纔會起作用。也就說說,只有在線程池線程數量超出corePoolSize了。我們纔會把超時的空閒線程給停止掉。否則就保持線程池中有 corePoolSize 個線程就可以了。

Unit:參數keepAliveTime的時間單位,就是 TimeUnit類當中的幾個屬性。

workQueue:用來存儲待執行任務的隊列,不同的線程池它的隊列實現方式不同(因爲這關係到排隊策略的問題)比如有以下幾種
ArrayBlockingQueue:基於數組的隊列,創建時需要指定大小。
LinkedBlockingQueue:基於鏈表的隊列,如果沒有指定大小,則默認值是 Integer.MAX_VALUE。(newFixedThreadPool和newSingleThreadExecutor使用的就是這種隊列)。
SynchronousQueue:這種隊列比較特殊,因爲不排隊就直接創建新線程把任務提交了。(newCachedThreadPool使用的就是這種隊列)。
這些都是阻塞式隊列,具體詳情: https://blog.csdn.net/weixin_43231076/article/details/90384023

threadFactory:線程工廠,用來創建線程。

Handler:拒絕執行任務時的策略,一般來講有以下四種策略,
(1) ThreadPoolExecutor.AbortPolicy 丟棄任務,並拋出 RejectedExecutionException 異常。
(2) ThreadPoolExecutor.CallerRunsPolicy:該任務被線程池拒絕,由調用 execute方法的線程執行該任務。
(3) ThreadPoolExecutor.DiscardOldestPolicy : 拋棄隊列最前面的任務,然後重新嘗試執行任務。
(4) ThreadPoolExecutor.DiscardPolicy,丟棄任務,不過也不拋出異常。

通過Executor創建四種線程池的方式

// 無限大小線程池 jvm自動回收
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();

//創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待
ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(5);

//創建一個定長線程池,支持定時及週期性任務執行
ScheduledExecutorService newScheduledThreadPool = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 10; i++) {
final int temp = i;
newScheduledThreadPool.schedule(new Runnable() {
public void run() {
System.out.println(“i:” + temp);
}
}, 3, TimeUnit.SECONDS);
}
表示線程延遲3秒啓動

//創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行
ExecutorService newSingleThreadExecutor = Executors.newSingleThreadExecutor();

線程池原理剖析

提交一個任務到線程池中,線程池的處理流程如下:
(1),如果當前線程池線程數目小於 corePoolSize(核心池還沒滿呢),那麼就創建一個新線程去處理任務。
(2),如果核心池已經滿了,來了一個新的任務後,會嘗試將其添加到任務隊列中,如果成功,則等待空閒線程將其從隊列中取出並且執行,如果隊列已經滿了,則繼續下一步。
(3),此時,如果線程池線程數量 小於 maximumPoolSize,則創建一個新線程執行任務,否則,那就說明線程池到了最大飽和能力了,沒辦法再處理了,此時就按照拒絕策略來處理。(就是構造函數當中的Handler對象)。
(4),如果線程池的線程數量大於corePoolSize,則當某個線程的空閒時間超過了keepAliveTime,那麼這個線程就要被銷燬了,直到線程池中線程數量不大於corePoolSize爲止。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章