在Java線程的實現/創建方式 - 池塘里洗澡的鴨子 - 博客園 (cnblogs.com)中介紹了集中線程創建的方式,最后一種是線程池的方式創建線程。那么線程池是基於什么樣的原理實現的呢?線程的復用。
線程池的主要工作是控制運行的線程的數量,處理過程中將任務放入隊列,然后在線程創建后啟動這些任務。如果所需線程數量超過了線程池的最大數量那么超出數量的線程排隊等候,等其它線程執行完畢,再從隊列中取出任務來執行。線程池的主要特點為: 線程復用; 控制最大並發數; 管理線程。
1、線程復用原理:
每一個 Thread 的類都有一個 start 方法。 當調用 start 啟動線程時 Java 虛擬機會調用該類的 run方法。 那么該類的 run() 方法中就是調用了 Runnable 對象的 run() 方法。 我們可以繼承重寫Thread 類,在其 start 方法中添加不斷循環調用傳遞過來的 Runnable 對象。 這就是線程池的實現原理, 循環方法中不斷獲取 Runnable 是用 Queue 實現的,在獲取下一個 Runnable 之前可以是阻塞的。
2、線程池的組成:
一般的線程池主要分為以下 4 個組成部分:
1)線程池管理器:用於創建並管理線程池
2)工作線程:線程池中的線程
3)任務接口:每個任務必須實現的接口,用於工作線程調度其運行
4)任務隊列:用於存放待處理的任務,提供一種緩沖機制
調用方不斷地向線程池中提交任務形成任務隊列;線程池中有一組線程,不斷地從隊列中取任務,這是一個典型的生產者—消費者模型。
Java 中的線程池是通過 Executor 框架實現的,該框架中用到了 Executor, Executors,ExecutorService, ThreadPoolExecutor , Callable 和 Future、 FutureTask 這幾個類。
3、拒絕策略:
線程池中的線程已經達到線程池設置的max,無法繼續為新任務服務,同時等待隊列也已經排滿了,再也塞不下新任務了。這時候我們就需要拒絕策略機制合理的處理這個問題。
JDK 內置的拒絕策略如下:
1)AbortPolicy : 直接拋出異常,阻止系統正常運行。
2)CallerRunsPolicy : 只要線程池未關閉,該策略直接在調用者線程中,運行當前被丟棄的任務。顯然這樣做不會真的丟棄任務,但是,任務提交線程的性能極有可能會急劇下降。
3)DiscardOldestPolicy : 丟棄最老的一個請求,也就是即將被執行的一個任務,並嘗試再次提交當前任務。
4)DiscardPolicy : 該策略默默地丟棄無法處理的任務,不予任何處理。如果允許任務丟失,這是最好的一種方案。
以上內置拒絕策略均實現了 RejectedExecutionHandler 接口,若以上策略仍無法滿足實際需要,完全可以自己擴展 RejectedExecutionHandler 接口。
4、Java 線程池工作過程:
1)線程池剛創建時,里面沒有一個線程。任務隊列是作為參數傳進來的。不過,就算隊列里面有任務,線程池也不會馬上執行它們。
2)當調用 execute() 方法添加一個任務時,線程池會做如下判斷:
a) 如果正在運行的線程數量小於 corePoolSize,那么馬上創建線程運行這個任務;
b) 如果正在運行的線程數量大於或等於 corePoolSize,那么將這個任務放入隊列;
c) 如果這時候隊列滿了,而且正在運行的線程數量小於 maximumPoolSize,那么還是要創建非核心線程立刻運行這個任務;
d) 如果隊列滿了,而且正在運行的線程數量大於或等於 maximumPoolSize,那么線程池會拋出異常 RejectExecutionException。
3)當一個線程完成任務時,它會從隊列中取下一個任務來執行。
4)當一個線程無事可做,超過一定的時間(keepAliveTime)時,線程池會進行判斷:如果當前運行的線程數大於 corePoolSize,那么這個線程就被停掉。所以線程池的所有任務完成后,它最終會收縮到 corePoolSize 的大小。