线程中这么调用类_「手撕面试官」谈谈你对JDK中Executor的理解?
歡迎關注頭條號:Java小野貓
前言
隨著當今處理器計算能力愈發強大,可用的核心數量越來越多,各個應用對其實現更高吞吐量的需求的不斷增長,多線程 API 變得非常流行。在此背景下,Java自JDK1.5 提供了自己的多線程框架,稱為 Executor 框架.
1. Executor 框架是什么?
1.1 簡介
Java Doc中是這么描述的
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. An Executor is normally used instead of explicitly creating threads.
執行提交的Runnable任務的對象。這個接口提供了一種將任務提交與如何運行每個任務的機制,包括線程的詳細信息使用、調度等。通常使用Executor而不是顯式地創建線程。
我們可以這么理解:Executor就是一個線程池框架,在開發中如果需要創建線程可優先考慮使用Executor,無論你需要多線程還是單線程,Executor為你提供了很多其他功能,包括線程狀態,生命周期的管理。
Executor 位于java.util.concurrent.Executors ,提供了用于創建工作線程的線程池的工廠方法。它包含一組用于有效管理工作線程的組件。Executor API 通過 Executors 將任務的執行與要執行的實際任務解耦。 這是 生產者-消費者 模式的一種實現。
浮現于腦海中的一個基本的問題是,當我們創建 java.lang.Thread 對象或調用實現了 Runnable/Callable 接口來實現多線程時,為什么需要線程池?
如果我們不采用線程池,為每一個請求都創建一個線程的話:
所有的這些因素都會導致系統吞吐量下降。線程池通過保持一些存活線程并重用這些線程來克服這個問題。當提交到線程池中的任務多于線程池最大任務數時,那些多余的任務將被放到一個隊列中。 一旦正在執行的線程有空閑了,它們會從隊列中取下一個任務來執行。JDK 中的 Executors中, 此任務隊列是沒有長度限制的。
1.2 實現
我們先來看一下Executor的實現關系。
還是蠻好理解的,正如Java優秀框架的一貫設計思路,頂級接口-次級接口-虛擬實現類-實現類。
Executor:執行者,java線程池框架的最上層父接口,地位類似于spring的BeanFactry、集合框架的Collection接口,在Executor這個接口中只有一個execute方法,該方法的作用是向線程池提交任務并執行。
ExecutorService:該接口繼承自Executor接口,添加了shutdown、shutdownAll、submit、invokeAll等一系列對線程的操作方法,該接口比較重要,在使用線程池框架的時候,經常用到該接口。
AbstractExecutorService:這是一個抽象類,實現ExecuotrService接口,
ThreadPoolExecutor:這是Java線程池最核心的一個類,該類繼承自AbstractExecutorService,主要功能是創建線程池,給任務分配線程資源,執行任務。
ScheduledExecutorSerivce 和 ScheduledThreadPoolExecutor 提供了另一種線程池:延遲執行和周期性執行的線程池。
Executors:這是一個靜態工廠類,該類定義了一系列靜態工廠方法,通過這些工廠方法可以返回各種不同的線程池。
2. Executors 的類型
現在我們已經了解了 Executors 是什么, 讓我們來看看不同類型的 Executors。
2.1 SingleThreadExecutor
此線程池 Executor 只有一個線程。它用于以順序方式的形式執行任務。如果此線程在執行任務時因異常而掛掉,則會創建一個新線程來替換此線程,后續任務將在新線程中執行。
ExecutorService executorService = Executors.newSingleThreadExecutor()2.2 FixedThreadPool(n)
顧名思義,它是一個擁有固定數量線程的線程池。提交給 Executor 的任務由固定的 n 個線程執行,如果有更多的任務,它們存儲在 LinkedBlockingQueue 里。這個數字 n 通常跟底層處理器支持的線程總數有關。
ExecutorService executorService = Executors.newFixedThreadPool(4);2.3 CachedThreadPool
該線程池主要用于執行大量短期并行任務的場景。與固定線程池不同,此線程池的線程數不受限制。如果所有的線程都在忙于執行任務并且又有新的任務到來了,這個線程池將創建一個新的線程并將其提交到 Executor。只要其中一個線程變為空閑,它就會執行新的任務。 如果一個線程有 60 秒的時間都是空閑的,它們將被結束生命周期并從緩存中刪除。
但是,如果管理得不合理,或者任務不是很短的,則線程池將包含大量的活動線程。這可能導致資源紊亂并因此導致性能下降。
ExecutorService executorService = Executors.newCachedThreadPool();2.4 ScheduledExecutor
當我們有一個需要定期運行的任務或者我們希望延遲某個任務時,就會使用此類型的 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:只有在當前任務完成后才會啟動延遲倒計時。
3. 對 Future 對象的理解
由于提交給Executor 的任務是異步的,需要有一個對象來接收Executor 的處理結果,這個對象就是java.util.concurrent.Future(類似于JS中的Promise)。
應用方式:
Future 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 時中斷任務執行。
4. Example: 創建和執行一個簡單的 Executor
我們現在將創建一個任務并嘗試在 fixed pool Executor 中執行它:
public class Task implements Callable { 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 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 時,這里有一些重要的變化。
Notes:如何合理配置線程池的大小
一般需要根據任務的類型來配置線程池大小:
如果是CPU密集型任務,就需要盡量壓榨CPU,參考值可以設為 NCPU+1
如果是IO密集型任務,參考值可以設置為2*NCPU
當然,這只是一個參考值,具體的設置還需要根據實際情況進行調整,比如可以先將線程池大小設置為參考值,再觀察任務運行情況和系統負載、資源利用率來進行適當調整。
讀者福利
歡迎做Java的朋友們私信我【資料】免費獲取免費的Java架構學習資料(里面有高可用、高并發、高性能及分布式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)
其中覆蓋了互聯網的方方面面,期間碰到各種產品各種場景下的各種問題,很值得大家借鑒和學習,擴展自己的技術廣度和知識面。
總結
以上是生活随笔為你收集整理的线程中这么调用类_「手撕面试官」谈谈你对JDK中Executor的理解?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 换硬币c语言编程_为什么大多数程序员都会
- 下一篇: psp能装安卓软件吗_客户crm 软件能