天天看點

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工廠類中提供的newScheduledThreadPool、newFixedThreadPool、newCachedThreadPool方法其實也隻是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為止。

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

繼續閱讀