Java——非同步呼叫
阿新 • • 發佈:2021-09-01
一、通過建立新執行緒
非同步呼叫的本質,其實是通過開啟一個新的執行緒來執行。如以下例子:
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()); }
資料結果如下所示:
主執行緒 =====> 開始 =====> 1627893837146 非同步執行緒 =====> 開始 =====> 1627893837200 主執行緒 =====> 結束 =====> 1627893839205 非同步執行緒 =====> 結束 =====> 1627893842212
二、通過執行緒池
因為非同步任務的實現本質的由新執行緒來執行任務,所以通過執行緒池的也可以實現非同步執行。寫法同我們利用執行緒池開啟多執行緒一樣。
但由於我們的目的不是執行多執行緒,而是非同步執行任務,所以一般需要另外一個執行緒就夠了。
因此區別於執行多執行緒任務的我們常用的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()); }
執行結果如下:
主執行緒 =====> 開始 =====> 1627895467578 非同步執行緒 =====> 開始 =====> 1627895467635 主執行緒 =====> 結束 =====> 1627895469644 非同步執行緒 =====> 結束 =====> 1627895472649
三、通過@Async註解
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
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
五、通過MQ
通過訊息佇列中介軟體實現非同步處理,也是很常見的一種方式,比如:RabbitMQ、RocketMQ等,這裡就不舉例了。
六、通過事件處理機制
我們專案中,某些場景下使用的是Google的EventBus。EventBus 是 Google.Guava 的事件處理機制,是觀察者模式(生產/消費模型)的一種實現。
類似的還有Spring的Event。
EventBus 優點:
- 相比 Observer 程式設計簡單方便
- 通過自定義引數可實現同步、非同步操作以及異常處理
- 單程序使用,無網路影響
EventBus缺點:
- 只能單程序使用
- 專案異常重啟或者退出不保證訊息持久化
如果需要分散式使用還是需要使用MQ。