
上篇《Java執行緒的6種狀態詳解及創建執行緒的4種方式》
前言:我們都知道,執行緒是稀有資源,系統頻繁創建會很大程度上影響服務器的使用效率,如果不加以限制,很容易就會把服務器資源耗盡,所以,我們可以通過創建執行緒池來管理這些執行緒,提升對執行緒的使用率,
1、什么是執行緒池?
簡而言之,執行緒池就是管理執行緒的一個容器,有任務需要處理時,會相繼判斷核心執行緒數是否還有空閑、執行緒池中的任務佇列是否已滿、是否超過執行緒池大小,然后呼叫或創建執行緒或者排隊,執行緒執行完任務后并不會立即被銷毀,而是仍然在執行緒池中等待下一個任務,如果超過存活時間還沒有新的任務就會被銷毀,通過這樣復用執行緒從而降低開銷,
2、使用執行緒池有什么優點?
可能有人就會問了,使用執行緒池有什么好處嗎?那不用說,好處自然是有滴,大概有以下:
1、提升執行緒池中執行緒的使用率,減少物件的創建、銷毀,
2、執行緒池的伸縮性對性能有較大的影響,使用執行緒池可以控制執行緒數,有效的提升服務器的使用資源,避免由于資源不足而發生宕機等問題,(創建太多執行緒,將會浪費一定的資源,有些執行緒未被充分使用;銷毀太多執行緒,將導致之后浪費時間再次創建它們;創建執行緒太慢,將會導致長時間的等待,性能變差;銷毀執行緒太慢,導致其它執行緒資源饑餓,)
3、執行緒池的核心作業流程(重要)
我們要使用執行緒池得先了解它是怎么作業的,流程如下圖,廢話不多說看圖就行,核心就是復用執行緒,降低開銷,

4、執行緒池的五種狀態生命周期
- RUNNING :能接受新提交的任務,并且也能處理阻塞佇列中的任務,
- SHUTDOWN:關閉狀態,不再接受新提交的任務,但卻可以繼續處理阻塞佇列中已保存的任務,在執行緒池處于 RUNNING 狀態時,呼叫 shutdown() 方法會使執行緒池進入到該狀態,(finalize() 方法在執行程序中也會呼叫 shutdown() 方法進入該狀態),
- STOP:不能接受新任務,也不處理佇列中的任務,會中斷正在處理任務的執行緒,在執行緒池處于 RUNNING 或 SHUTDOWN 狀態時,呼叫 shutdownNow() 方法會使執行緒池進入到該狀態,
- TIDYING:如果所有的任務都已終止了,workerCount (有效執行緒數) 為0,執行緒池進入該狀態后會呼叫 terminated() 方法進入 TERMINATED 狀態,
- TERMINATED:在 terminated() 方法執行完后進入該狀態,默認 terminated() 方法中什么也沒有做,

5、創建執行緒池的幾種方式
- 通過 Executors 工廠方法創建
- 通過 new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue
workQueue) 自定義創建
相對而言,更建議用第二個創建執行緒池,Executors 創建的執行緒池內部很多地方用到了無界任務佇列,在高并發場景下,無界任務佇列會接收過多的任務物件,嚴重情況下會導致 JVM 崩潰,一些大廠也是禁止使用 Executors 工廠方法去創建執行緒池,newFixedThreadPool 和 newSingleThreadExecutor 的主要問題是堆積的請求處理佇列可能會耗費非常大的記憶體,甚至 OOM;newCachedThreadPool 和 newScheduledThreadPool 的主要問題是執行緒數最大數是 Integer.MAX_VALUE,可能會創建數量非常多的執行緒,甚至 OOM,
5.1、Executors 五個工廠方法創建不同執行緒池的區別

1、newCachedThreadPool()(作業佇列使用的是 SynchronousQueue)
創建一個執行緒池,如果執行緒池中的執行緒數量過大,它可以有效的回收多余的執行緒,如果執行緒數不足,那么它可以創建新的執行緒,
不足:這種方式雖然可以根據業務場景自動的擴展執行緒數來處理我們的業務,但是最多需要多少個執行緒同時處理卻是我們無法控制的,
優點:如果當第二個任務開始,第一個任務已經執行結束,那么第二個任務會復用第一個任務創建的執行緒,并不會重新創建新的執行緒,提高了執行緒的復用率,
作用:該方法回傳一個可以根據實際情況調整執行緒池中執行緒的數量的執行緒池,即該執行緒池中的執行緒數量不確定,是根據實際情況動態調整的,
2、newFixedThreadPool()(作業佇列使用的是 LinkedBlockingQueue)
這種方式可以指定執行緒池中的執行緒數,如果滿了后又來了新任務,此時只能排隊等待,
優點:newFixedThreadPool 的執行緒數是可以進行控制的,因此我們可以通過控制最大執行緒來使我們的服務器達到最大的使用率,同時又可以保證即使流量突然增大也不會占用服務器過多的資源,
作用:該方法回傳一個固定執行緒數量的執行緒池,該執行緒池中的執行緒數量始終不變,即不會再創建新的執行緒,也不會銷毀已經創建好的執行緒,自始自終都是那幾個固定的執行緒在作業,所以該執行緒池可以控制執行緒的最大并發數,
3、newScheduledThreadPool()
該執行緒池支持定時,以及周期性的任務執行,我們可以延遲任務的執行時間,也可以設定一個周期性的時間讓任務重復執行,該執行緒池中有以下兩種延遲的方法,
scheduleAtFixedRate 不同的地方是任務的執行時間,如果間隔時間大于任務的執行時間,任務不受執行時間的影響,如果間隔時間小于任務的執行時間,那么任務執行結束之后,會立馬執行,至此間隔時間就會被打亂,
scheduleWithFixedDelay 的間隔時間不會受任務執行時間長短的影響,
作用:該方法回傳一個可以控制執行緒池內執行緒定時或周期性執行某任務的執行緒池,
4、newSingleThreadExecutor()
這是一個單執行緒池,至始至終都由一個執行緒來執行,
作用:該方法回傳一個只有一個執行緒的執行緒池,即每次只能執行一個執行緒任務,多余的任務會保存到一個任務佇列中,等待這一個執行緒空閑,當這個執行緒空閑了再按 FIFO 方式順序執行任務佇列中的任務,
5、newSingleThreadScheduledExecutor()
只有一個執行緒,用來調度任務在指定時間執行,
作用:該方法回傳一個可以控制執行緒池內執行緒定時或周期性執行某任務的執行緒池,只不過和上面的區別是該執行緒池大小為 1,而上面的可以指定執行緒池的大小,
使用示例:
//創建一個會根據需要創建新執行緒的執行緒池
ExecutorService executor= Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
executor.submit(new Runnable() {
@Override
public void run() {
System.out.println(i);
}
});
}
這五種執行緒池都是直接或者間接獲取的 ThreadPoolExecutor 實體 ,只是實體化時傳遞的引數不一樣,所以如果 Java 提供的執行緒池滿足不了我們的需求,我們可以通過 ThreadPoolExecutor 構造方法創建自定義執行緒池,
5.2、ThreadPoolExecutor 構造方法引數詳解
public ThreadPoolExecutor(
int corePoolSize,//執行緒池核心執行緒大小
int maximumPoolSize,//執行緒池最大執行緒數量
long keepAliveTime,//空閑執行緒存活時間
TimeUnit unit,//空閑執行緒存活時間單位,一共有七種靜態屬性(TimeUnit.DAYS天,TimeUnit.HOURS小時,TimeUnit.MINUTES分鐘,TimeUnit.SECONDS秒,TimeUnit.MILLISECONDS毫秒,TimeUnit.MICROSECONDS微妙,TimeUnit.NANOSECONDS納秒)
BlockingQueue<Runnable> workQueue,//作業佇列
ThreadFactory threadFactory,//執行緒工廠,主要用來創建執行緒(默認的工廠方法是:Executors.defaultThreadFactory()對執行緒進行安全檢查并命名)
RejectedExecutionHandler handler//拒絕策略(默認是:ThreadPoolExecutor.AbortPolicy不執行并拋出例外)
)
使用示例:
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 20, 2, TimeUnit.SECONDS, new LinkedBlockingQueue<>(5));
5.2.1、作業佇列
jdk 中提供了四種作業佇列:
①ArrayBlockingQueue
基于陣列的有界阻塞佇列,按 FIFO 排序,新任務進來后,會放到該佇列的隊尾,有界的陣列可以防止資源耗盡問題,當執行緒池中執行緒數量達到 corePoolSize 后,再有新任務進來,則會將任務放入該佇列的隊尾,等待被調度,如果佇列已經是滿的,則創建一個新執行緒,如果執行緒數量已經達到 maxPoolSize,則會執行拒絕策略,
②LinkedBlockingQuene
基于鏈表的無界阻塞佇列(其實最大容量為 Interger.MAX_VALUE),按照 FIFO 排序,由于該佇列的近似無界性,當執行緒池中執行緒數量達到 corePoolSize 后,再有新任務進來,會一直存入該佇列,而不會去創建新執行緒直到 maxPoolSize,因此使用該作業佇列時,引數 maxPoolSize 其實是不起作用的,
③SynchronousQuene
一個不快取任務的阻塞佇列,生產者放入一個任務必須等到消費者取出這個任務,也就是說新任務進來時,不會快取,而是直接被調度執行該任務,如果沒有可用執行緒,則創建新執行緒,如果執行緒數量達到 maxPoolSize,則執行拒絕策略,
④PriorityBlockingQueue
具有優先級的無界阻塞佇列,優先級通過引數 Comparator 實作,
5.2.2、拒絕策略
當作業佇列中的任務已到達最大限制,并且執行緒池中的執行緒數量也達到最大限制,這時如果有新任務提交進來,就會執行拒絕策略,jdk中提供了4中拒絕策略:
①ThreadPoolExecutor.CallerRunsPolicy
該策略下,在呼叫者執行緒中直接執行被拒絕任務的 run 方法,除非執行緒池已經 shutdown,則直接拋棄任務,
②ThreadPoolExecutor.AbortPolicy
該策略下,直接丟棄任務,并拋出 RejectedExecutionException 例外,
③ThreadPoolExecutor.DiscardPolicy
該策略下,直接丟棄任務,什么都不做,
④ThreadPoolExecutor.DiscardOldestPolicy
該策略下,拋棄進入佇列最早的那個任務,然后嘗試把這次拒絕的任務放入佇列,
除此之外,還可以根據應用場景需要來實作 RejectedExecutionHandler 介面自定義策略,
6、執行緒池的關閉
- shutdown():
1、呼叫之后不允許繼續往執行緒池內添加執行緒;
2、執行緒池的狀態變為 SHUTDOWN 狀態;
3、所有在呼叫 shutdown() 方法之前提交到 ExecutorSrvice 的任務都會執行;
4、一旦所有執行緒結束執行當前任務,ExecutorService 才會真正關閉, - shutdownNow():
1、該方法回傳尚未執行的 task 的 List;
2、執行緒池的狀態變為 STOP 狀態;
3、嘗試停止所有的正在執行或暫停任務的執行緒,
簡單點來說,就是:
shutdown() 呼叫后,不可以再 submit 新的 task,已經 submit 的將繼續執行
shutdownNow() 呼叫后,試圖停止當前正在執行的 task,并回傳尚未執行的 task 的 list
7、總結
本文簡單介紹了執行緒池的一些相關知識,相信大家對執行緒池的優點,執行緒池的生命周期,執行緒池的作業流程及執行緒池的使用有了一個大概的了解,也希望能對有需要的人提供一點幫助!文中有錯誤的地方,還請留言給予指正,謝謝~
也歡迎大家關注我的公眾號:Java的成神之路,免費領取最新面試資料,技術電子書,架構進階相關資料等,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/195965.html
標籤:其他
上一篇:萬圣節快到了,讓我們用Python畫一只蝙蝠圖表吧(附代碼)
下一篇:Delphi 2007 呼叫 Delphi Xe 撰寫DLL 不能顯示DLL中例外的明細資訊,有誰知道怎么解決?
