隨着當今處理器計算能力愈發強大,可用的核心數量愈來愈多,各個應用對其實現更高吞吐量的需求的不斷增加,多線程 API 變得很是流行。在此背景下,Java自JDK1.5 提供了本身的多線程框架,稱爲 Executor 框架.html
Java Doc中是這麼描述的java
An object that executes submitted
Runnable
tasks. This interface provides a way of decoupling task submission from the mechanics of how each task will be run, including details of thread use, scheduling, etc. AnExecutor
is normally used instead of explicitly creating threads.程序員執行提交的Runnable任務的對象。這個接口提供了一種將任務提交與如何運行每一個任務的機制,包括線程的詳細信息使用、調度等。一般使用Executor而不是顯式地建立線程。spring
咱們能夠這麼理解:Executor就是一個線程池框架,在開發中若是須要建立線程可優先考慮使用Executor,不管你須要多線程仍是單線程,Executor爲你提供了不少其餘功能,包括線程狀態,生命週期的管理。api
Executor 位於java.util.concurrent.Executors
,提供了用於建立工做線程的線程池的工廠方法。它包含一組用於有效管理工做線程的組件。Executor API 經過 Executors
將任務的執行與要執行的實際任務解耦。 這是 生產者-消費者
模式的一種實現。緩存
浮現於腦海中的一個基本的問題是,當咱們建立 java.lang.Thread
對象或調用實現了 Runnable
/Callable
接口來實現多線程時,爲何須要線程池?bash
若是咱們不採用線程池,爲每個請求都建立一個線程的話:多線程
全部的這些因素都會致使系統吞吐量降低。線程池經過保持一些存活線程並重用這些線程來克服這個問題。當提交到線程池中的任務多於線程池最大任務數時,那些多餘的任務將被放到一個隊列
中。 一旦正在執行的線程有空閒了,它們會從隊列中取下一個任務來執行。JDK 中的 Executors中, 此任務隊列是沒有長度限制的。oracle
咱們先來看一下Executor的實現關係。框架
仍是蠻好理解的,正如Java優秀框架的一向設計思路,頂級接口-次級接口-虛擬實現類-實現類。
**Executor:**執行者,java線程池框架的最上層父接口,地位相似於spring的BeanFactry、集合框架的Collection接口,在Executor這個接口中只有一個execute方法,該方法的做用是向線程池提交任務並執行。
**ExecutorService:**該接口繼承自Executor接口,添加了shutdown、shutdownAll、submit、invokeAll等一系列對線程的操做方法,該接口比較重要,在使用線程池框架的時候,常常用到該接口。
**AbstractExecutorService:**這是一個抽象類,實現ExecuotrService接口,
**ThreadPoolExecutor:**這是Java線程池最核心的一個類,該類繼承自AbstractExecutorService,主要功能是建立線程池,給任務分配線程資源,執行任務。
ScheduledExecutorSerivce 和 ScheduledThreadPoolExecutor 提供了另外一種線程池:延遲執行和週期性執行的線程池。
**Executors:**這是一個靜態工廠類,該類定義了一系列靜態工廠方法,經過這些工廠方法能夠返回各類不一樣的線程池。
如今咱們已經瞭解了 Executors 是什麼, 讓咱們來看看不一樣類型的 Executors。
此線程池 Executor 只有一個線程。它用於以順序方式的形式執行任務。若是此線程在執行任務時因異常而掛掉,則會建立一個新線程來替換此線程,後續任務將在新線程中執行。
ExecutorService executorService = Executors.newSingleThreadExecutor()
複製代碼
顧名思義,它是一個擁有固定數量線程的線程池。提交給 Executor 的任務由固定的 n
個線程執行,若是有更多的任務,它們存儲在 LinkedBlockingQueue
裏。這個數字 n
一般跟底層處理器支持的線程總數有關。
ExecutorService executorService = Executors.newFixedThreadPool(4);
複製代碼
該線程池主要用於執行大量短時間並行任務的場景。與固定線程池不一樣,此線程池的線程數不受限制。若是全部的線程都在忙於執行任務而且又有新的任務到來了,這個線程池將建立一個新的線程並將其提交到 Executor。只要其中一個線程變爲空閒,它就會執行新的任務。 若是一個線程有 60 秒的時間都是空閒的,它們將被結束生命週期並從緩存中刪除。
可是,若是管理得不合理,或者任務不是很短的,則線程池將包含大量的活動線程。這可能致使資源紊亂並所以致使性能降低。
ExecutorService executorService = Executors.newCachedThreadPool();
複製代碼
當咱們有一個須要按期運行的任務或者咱們但願延遲某個任務時,就會使用此類型的 executor。
ScheduledExecutorService scheduledExecService = Executors.newScheduledThreadPool(1);
複製代碼
可使用 scheduleAtFixedRate
或 scheduleWithFixedDelay
在 ScheduledExecutor
中按期的執行任務。
scheduledExecService.scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
scheduledExecService.scheduleWithFixedDelay(Runnable command, long initialDelay, long period, TimeUnit unit)
複製代碼
這兩種方法的主要區別在於它們對連續執行按期任務之間的延遲的應答。
scheduleAtFixedRate
:不管前一個任務什麼時候結束,都以固定間隔執行任務。
scheduleWithFixedDelay
:只有在當前任務完成後纔會啓動延遲倒計時。
因爲提交給Executor 的任務是異步的,須要有一個對象來接收Executor 的處理結果,這個對象就是java.util.concurrent.Future
(相似於JS中的Promise)。
應用方式:
Future<String> result = executorService.submit(callableTask);
複製代碼
調用者能夠繼續執行主程序,當須要提交任務的結果時,他能夠在這個 Future
對象上調用.get()
方法來獲取。若是任務完成,結果將當即返回給調用者,不然調用者將被阻塞,直到 Executor 完成此操做的執行並計算出結果。(瞭解JS的童鞋此處能夠和Promise的then()相類比)。
若是調用者不能無限期地等待任務執行的結果,那麼這個等待時間也能夠設置爲定時地。能夠經過 Future.get(long timeout,TimeUnit unit)
方法實現,若是在規定的時間範圍內沒有返回結果,則拋出 TimeoutException
。調用者能夠處理此異常並繼續執行該程序。
若是在執行任務時出現異常,則對 get 方法的調用將拋出一個ExecutionException
。
對於 Future.get()
方法返回的結果,一個重要的事情是,只有提交的任務實現了java.util.concurrent.Callable
接口時才返回 Future
。若是任務實現了Runnable
接口,那麼一旦任務完成,對 .get()
方法的調用將返回 null
。
另外一點是 Future.cancel(boolean mayInterruptIfRunning)
方法。此方法用於取消已提交任務的執行。若是任務已在執行,則 Executor 將嘗試在mayInterruptIfRunning
標誌爲 true
時中斷任務執行。
咱們如今將建立一個任務並嘗試在 fixed pool Executor 中執行它:
public class Task implements Callable<String> {
private String message;
public Task(String message) {
this.message = message;
}
@Override
public String call() throws Exception {
return "Hello " + message + "!";
}
}
複製代碼
Task
類實現 Callable
接口並有一個 String
類型做爲返回值的方法。 這個方法也能夠拋出 Exception
。這種向 Executor 拋出異常的能力以及 Executor 將此異常返回給調用者的能力很是重要,由於它有助於調用者知道任務執行的狀態。
如今讓咱們來執行一下這個任務:
public class ExecutorExample {
public static void main(String[] args) {
Task task = new Task("World");
ExecutorService executorService = Executors.newFixedThreadPool(4);
Future<String> result = executorService.submit(task);
try {
System.out.println(result.get());
} catch (InterruptedException | ExecutionException e) {
System.out.println("Error occured while executing the submitted task");
e.printStackTrace();
}
executorService.shutdown();
}
}
複製代碼
咱們建立了一個具備4個線程數的 FixedThreadPool
Executors,並實例化了 Task
類,並將它提交給 Executors 執行。 結果由 Future
對象返回,而後咱們在屏幕上打印。
讓咱們運行 ExecutorExample
並查看其輸出:
Hello World!
複製代碼
最後,咱們調用 executorService
對象上的 shutdown 來終止全部線程並將資源返回給 OS。
shutdown()
方法等待 Executor 完成當前提交的任務。 可是,若是要求是當即關閉 Executor 而不等待,那麼咱們可使用 shutdownNow()
方法。
任何待執行的任務都將結果返回到 java.util.List
對象中。
咱們也能夠經過實現 Runnable
接口來建立一樣的任務:
public class Task implements Runnable{
private String message;
public Task(String message) {
this.message = message;
}
public void run() {
System.out.println("Hello " + message + "!");
}
}
複製代碼
當咱們實現 Runnable 時,這裏有一些重要的變化。
run()
方法獲得任務執行的結果。 所以,咱們直接在這裏打印。run()
方法不可拋出任何已受檢的異常。Notes:如何合理配置線程池的大小
通常須要根據任務的類型來配置線程池大小:
若是是CPU密集型任務,就須要儘可能壓榨CPU,參考值能夠設爲 NCPU+1 若是是IO密集型任務,參考值能夠設置爲2*NCPU 固然,這只是一個參考值,具體的設置還須要根據實際狀況進行調整,好比能夠先將線程池大小設置爲參考值,再觀察任務運行狀況和系統負載、資源利用率來進行適當調整。
您的點贊與關注是對做者寫做的最大的支持,謝謝!
歡迎各位關注我的公衆號(關注後Java程序員必備Spring Mybatics以及其餘框架的入門和拔高視頻哦)