平常開發中,會常常遇到說,前臺調服務,而後觸發一個比較耗時的異步服務,且不用等異步任務的處理結果就對原服務進行返回。這裏就涉及的Java異步調用的一個知識。下面本文嘗試將Java異步調用的多種方式進行概括。html
首先的咱們得認識到,異步調用的本質,實際上是經過開啓一個新的線程來執行。如如下例子:編程
public static void main(String[] args) throws Exception{ System.out.println("主線程 =====> 開始 =====> " + System.currentTimeMillis()); new Thread(() -> { System.out.println("異步線程 =====> 開始 =====> " + System.currentTimeMillis()); try{ Thread.sleep(5000); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println("異步線程 =====> 結束 =====> " + System.currentTimeMillis()); }).start(); Thread.sleep(2000); System.out.println("主線程 =====> 結束 =====> " + System.currentTimeMillis()); }
數據結果以下所示,咱們知道,System.currentTimeMillis()
時間單位爲ms。markdown
主線程 =====> 開始 =====> 1627893837146 異步線程 =====> 開始 =====> 1627893837200 主線程 =====> 結束 =====> 1627893839205 異步線程 =====> 結束 =====> 1627893842212
咱們經過線程休眠來達成主線程執行時間2秒左右,異步線程執行5秒左右的效果。經過打印出來的時間戳倒數第四位(秒位)咱們能夠看出,兩個的線程執行總時間爲5秒左右,符合異步執行的特徵多線程
以上是採用Runable實現多線程建立方式的lambda寫法,關於的lambda知識,可參考Java Lambda 表達式;而關於多線程的多種實現方式,Java多線程事務管理一文有說起,可移步查看架構
由於異步任務的實現本質的由新線程來執行任務,因此經過線程池的也能夠實現異步執行。寫法同咱們利用線程池開啓多線程同樣。但因爲咱們的目的不是執行多線程,而是異步執行任務,因此通常須要另一個線程就夠了。框架
所以區別於執行多線程任務的咱們經常使用的newFixedThreadPool
,在執行異步任務時,咱們用newSingleThreadExecutor
來建立一個單個線程的線程池。異步
public static void main(String[] args) throws Exception{ System.out.println("主線程 =====> 開始 =====> " + System.currentTimeMillis()); ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.submit(()->{ System.out.println("異步線程 =====> 開始 =====> " + System.currentTimeMillis()); try{ Thread.sleep(5000); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println("異步線程 =====> 結束 =====> " + System.currentTimeMillis()); }); executorService.shutdown(); // 回收線程池 Thread.sleep(2000); System.out.println("主線程 =====> 結束 =====> " + System.currentTimeMillis()); }
執行結果以下:jvm
主線程 =====> 開始 =====> 1627895467578 異步線程 =====> 開始 =====> 1627895467635 主線程 =====> 結束 =====> 1627895469644 異步線程 =====> 結束 =====> 1627895472649
能夠看到,結果跟第一種結果是基本一致的。async
舒適提示:不要忘記線程池的回收ide
咱們都知道,SpringBoot項目有一個的很重要的特色就是的註解化。若是你的項目是SpringBoot,那就又多了一種選擇——@Async註解。
使用起來也很是簡單,將要異步執行的代碼封裝成一個方法,而後用@Async註解該方法,而後在主方法中直接調用就行。
@Test public void mainThread() throws Exception{ System.out.println("主線程 =====> 開始 =====> " + System.currentTimeMillis()); collectionBill.asyncThread(); Thread.sleep(2000); System.out.println("主線程 =====> 結束 =====> " + System.currentTimeMillis()); Thread.sleep(4000); // 用於防止jvm中止,致使異步線程中斷 }
@Async public void asyncThread(){ System.out.println("異步線程 =====> 開始 =====> " + System.currentTimeMillis()); try{ Thread.sleep(5000); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println("異步線程 =====> 結束 =====> " + System.currentTimeMillis()); }
執行結果以下:
主線程 =====> 開始 =====> 1627897539948 異步線程 =====> 開始 =====> 1627897539956 主線程 =====> 結束 =====> 1627897541965 異步線程 =====> 結束 =====> 1627897544966
有如下兩點須要注意:
- 相似
@Tranctional
註解,@Async
註解的方法與調用方法不能在同一個類中,不然不生效- JUnit框架的設計不考慮多線程場景,因此主線程退出後,子線程也會跟着當即退出,因此能夠在後面加多線程休眠時間來觀察異步線程的執行狀況
CompletableFuture是JDK1.8的新特性,是對Future的擴展。CompletableFuture實現了CompletionStage接口和Future接口,增長了異步回調、流式處理、多個Future組合處理的能力。
實現代碼以下:
public static void main(String[] args) throws Exception{ System.out.println("主線程 =====> 開始 =====> " + System.currentTimeMillis()); ExecutorService executorService = Executors.newSingleThreadExecutor(); CompletableFuture.runAsync(() ->{ System.out.println("異步線程 =====> 開始 =====> " + System.currentTimeMillis()); try{ Thread.sleep(5000); }catch (InterruptedException e){ e.printStackTrace(); } System.out.println("異步線程 =====> 結束 =====> " + System.currentTimeMillis()); },executorService); executorService.shutdown(); // 回收線程池 Thread.sleep(2000); System.out.println("主線程 =====> 結束 =====> " + System.currentTimeMillis()); }
一樣能夠實現相似的結果以下:
主線程 =====> 開始 =====> 1627898354914 異步線程 =====> 開始 =====> 1627898354977 主線程 =====> 結束 =====> 1627898356980 異步線程 =====> 結束 =====> 1627898359979
CompletableFuture有者很是強大的功能,能給咱們帶來很是絲滑的編程體驗。後續會寫一篇文章來詳細介紹CompletableFuture
分類: [Java基礎]
標籤: [多線程]