ThreadPoolExecutor是Java語言對於線程池的實現。池化技術是一種複用資源,減小開銷的技術。線程是操做系統的資源,線程的建立與調度由操做系統負責,線程的建立與調度都要耗費大量的資源,其中線程建立須要佔用必定的內存,而線程的調度須要不斷的切換線程上下文形成必定的開銷。同時線程執行完畢以後就會被操做系統回收,這樣在高併發狀況下就會形成系統頻繁建立線程。java
爲此線程池技術爲了解決上述問題,使線程在使用完畢後不回收而是重複利用。若是線程可以複用,那麼咱們就能夠使用固定數量的線程來解決併發問題,這樣一來不只節約了系統資源,並且也會減小線程上下文切換的開銷。shell
ThreadPoolExecutor的構造函數有7個,它們分別是:併發
通常狀況下,咱們只使用前五個參數,剩餘兩個咱們使用默認參數便可。函數
其實,線程池建立參數都與線程池的任務提交邏輯密切相關。根據源碼描述能夠得知:當提交一個新任務時(執行線程池的execute方法)會通過三個步驟的處理。高併發
corePoolSize
時,線程池會建立一個新的線程(建立新線程由傳入參數threadFactory
完成)來處理任務,哪怕線程池中有空閒線程,依然會選擇建立新線程來處理。corePoolSize
時,線程池會將新任務壓入工做隊列(參數中傳遞的workQueue
)等待調度。maximumPoolSize
。若是小於maximunPoolSize
則會新建線程來處理任務(這時咱們的keepAliveTime
參數就起做用了,它主要做用於這種狀況下建立的線程,若是任務數量減少,這些線程閒置了,那麼在超過keepAliveTime
時間後就會被回收)。若是大於了maximumPoolSize
就會交由任務拒絕處理器handler
處理。正如線程有不一樣的狀態同樣,線程池也擁有不一樣的運行狀態。源碼中提出,線程池有五種狀態,分別爲:測試
文章開頭說到,線程在執行完畢以後會被操做系統回收銷燬,那麼線程池時如何保障線程不被銷燬?首先看一個測試用例:操作系統
public static void testThreadState() { Thread thread = new Thread(() -> System.out.println("Hello world")); // 建立一個線程 System.out.println(thread.getState()); // 此時線程的狀態爲NEW thread.start(); // 啓動線程,狀態爲RUNNING System.out.println(thread.getState()); try { thread.join(); System.out.println(thread.getState()); // 線程運行結束,狀態爲TERMINATED thread.start(); // 此時再啓動線程會發生什麼呢? } catch (InterruptedException e) { e.printStackTrace(); } }
結果輸出:線程
NEW RUNNABLE Hello world TERMINATED Exception in thread "main" java.lang.IllegalThreadStateException at java.base/java.lang.Thread.start(Thread.java:794) at misc.ThreadPoolExecutorTest.testThreadState(ThreadPoolExecutorTest.java:90) at misc.ThreadPoolExecutorTest.main(ThreadPoolExecutorTest.java:114)
能夠看出,當一個線程運行結束以後,咱們是不可能讓線程起死回生從新啓動的。既然如此ThreadPoolExecutor如何保障線程執行完一個任務不被銷燬而繼續執行下一個任務呢?code
其實這裏就要講到咱們最開始傳入的參數workQueue
,它的接口類型爲BlockingQueue<T>
,直譯過來就是阻塞隊列。這中隊列有個特色,就是當隊列爲空而嘗試出隊操做時會阻塞。blog
基於阻塞隊列的如上特色,ThreadPoolExecutor採用不斷循環+阻塞隊列的方式來實現線程不被銷燬。
final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); // allow interrupts boolean completedAbruptly = true; try { // 從工做隊列中不斷取任務。若是工做隊列爲空,那麼程序會阻塞在這裏 while (task != null || (task = getTask()) != null) { w.lock(); // 檢查線程池狀態 if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, task); try { //// 執行任務 //// task.run(); afterExecute(task, null); } catch (Throwable ex) { afterExecute(task, ex); throw ex; } } finally { task = null; w.completedTasks++; w.unlock(); } } completedAbruptly = false; } finally { processWorkerExit(w, completedAbruptly); } }
想要關閉線程池能夠經過調用shutdown()
和shutdownNow()
方法實現。兩種方法有所不一樣,其中調用shutdown()
方法會中止接收新的任務,處理工做隊列中的任務,調用這個方法以後線程池會進入SHUTDOWN狀態,此方法無返回值而且不拋出異常。
而shutdownNow()
方法會中止接收新的任務,並且會返回未完成的任務集合,同時這個方法也會拋出異常。
線程池建立有七個參數,這幾個參數的相互做用能夠建立出適應特定業務場景的線程池。其中最爲重要的有三個參數分別爲:corePoolSize
,maximumPoolSize
,workQueue
。其中前兩個參數已經在上文中做了詳細介紹,而workQueue
參數在線程池建立中也極爲重要。workQueue
主要有三種:
經過上述三種隊列的特性咱們能夠得知,