
日常開發(fā)中,會經(jīng)常遇到說,前臺調(diào)服務,然后觸發(fā)一個比較耗時的異步服務,且不用等異步任務的處理結(jié)果就對原服務進行返回。這里就涉及的Java異步調(diào)用的一個知識。下面本文嘗試將Java異步調(diào)用的多種方式進行歸納。
一、通過創(chuàng)建新線程
首先的我們得認識到,異步調(diào)用的本質(zhì),其實是通過開啟一個新的線程來執(zhí)行。如以下例子:
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("異步線程 =====> 結(jié)束 =====> " + System.currentTimeMillis());
}).start();
Thread.sleep(2000);
System.out.println("主線程 =====> 結(jié)束 =====> " + System.currentTimeMillis());
}
數(shù)據(jù)結(jié)果如下所示,我們知道,System.currentTimeMillis()時間單位為ms。
主線程 =====> 開始 =====> 1627893837146
異步線程 =====> 開始 =====> 1627893837200
主線程 =====> 結(jié)束 =====> 1627893839205
異步線程 =====> 結(jié)束 =====> 1627893842212
我們通過線程休眠來達成主線程執(zhí)行時間2秒左右,異步線程執(zhí)行5秒左右的效果。通過打印出來的時間戳倒數(shù)第四位(秒位)我們可以看出,兩個的線程執(zhí)行總時間為5秒左右,符合異步執(zhí)行的特征
以上是采用Runable實現(xiàn)多線程創(chuàng)建方式的lambda寫法,關于的lambda知識,可參考Java Lambda 表達式;而關于多線程的多種實現(xiàn)方式,Java多線程事務管理一文有提及,可移步查看
二、通過線程池
因為異步任務的實現(xiàn)本質(zhì)的由新線程來執(zhí)行任務,所以通過線程池的也可以實現(xiàn)異步執(zhí)行。寫法同我們利用線程池開啟多線程一樣。但由于我們的目的不是執(zhí)行多線程,而是異步執(zhí)行任務,所以一般需要另外一個線程就夠了。
因此區(qū)別于執(zhí)行多線程任務的我們常用的newFixedThreadPool,在執(zhí)行異步任務時,我們用newSingleThreadExecutor 來創(chuàng)建一個單個線程的線程池。
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("異步線程 =====> 結(jié)束 =====> " + System.currentTimeMillis());
});
executorService.shutdown(); // 回收線程池
Thread.sleep(2000);
System.out.println("主線程 =====> 結(jié)束 =====> " + System.currentTimeMillis());
}
執(zhí)行結(jié)果如下:
主線程 =====> 開始 =====> 1627895467578
異步線程 =====> 開始 =====> 1627895467635
主線程 =====> 結(jié)束 =====> 1627895469644
異步線程 =====> 結(jié)束 =====> 1627895472649
可以看到,結(jié)果跟第一種結(jié)果是基本一致的。
溫馨提示:不要忘記線程池的回收
三、通過@Async注解
我們都知道,SpringBoot項目有一個的很重要的特點就是的注解化。如果你的項目是SpringBoot,那就又多了一種選擇——@Async注解。
使用起來也非常簡單,將要異步執(zhí)行的代碼封裝成一個方法,然后用@Async注解該方法,然后在主方法中直接調(diào)用就行。
@Test
public void mainThread() throws Exception{
System.out.println("主線程 =====> 開始 =====> " + System.currentTimeMillis());
collectionBill.asyncThread();
Thread.sleep(2000);
System.out.println("主線程 =====> 結(jié)束 =====> " + 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("異步線程 =====> 結(jié)束 =====> " + System.currentTimeMillis());
}
執(zhí)行結(jié)果如下:
主線程 =====> 開始 =====> 1627897539948
異步線程 =====> 開始 =====> 1627897539956
主線程 =====> 結(jié)束 =====> 1627897541965
異步線程 =====> 結(jié)束 =====> 1627897544966
有以下兩點需要注意:
- 類似
@Tranctional注解,@Async注解的方法與調(diào)用方法不能在同一個類中,否則不生效- JUnit框架的設計不考慮多線程場景,所以主線程退出后,子線程也會跟著立即退出,所以可以在后面加多線程休眠時間來觀察異步線程的執(zhí)行情況
四、通過CompletableFuture
CompletableFuture是JDK1.8的新特性,是對Future的擴展。CompletableFuture實現(xiàn)了CompletionStage接口和Future接口,增加了異步回調(diào)、流式處理、多個Future組合處理的能力。
實現(xiàn)代碼如下:
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("異步線程 =====> 結(jié)束 =====> " + System.currentTimeMillis());
},executorService);
executorService.shutdown(); // 回收線程池
Thread.sleep(2000);
System.out.println("主線程 =====> 結(jié)束 =====> " + System.currentTimeMillis());
}
同樣可以實現(xiàn)類似的結(jié)果如下:
主線程 =====> 開始 =====> 1627898354914
異步線程 =====> 開始 =====> 1627898354977
主線程 =====> 結(jié)束 =====> 1627898356980
異步線程 =====> 結(jié)束 =====> 1627898359979
CompletableFuture有者非常強大的功能,能給我們帶來非常絲滑的編程體驗。后續(xù)會寫一篇文章來詳細介紹CompletableFuture
分類: [Java基礎]
標簽: [多線程]