首先,我們先聊聊異步調用?那麼什麼是異步調用呢?與異步調用對應的應該是同步調用,關于同步我們應該不陌生,就是我們的程式調用或者是接口調用是按照一定的順序進行執行的,每個過程都是需要等待上一個過程完成之後才執行,不會出現過程之間的互相交替執行的情況。
而異步調用則不同,雖然我們的程式是順序執行的,但是在調用的過程中一個過程的執行不需要等待到另一個過程執行完成傳回之後就可以執行,這種操作我們就把它稱為是異步調用。
下面我們就先來通過幾個例子來看一下同步調用
同步調用
第一步、首先我們來開發一個任務執行類,在這個任務執行類中有三個随機時間的任務等待執行。
@Component
public class Task {
public static Random random = new Random();
public void doTackOne() throws InterruptedException {
TimeUnit.SECONDS.sleep(random.nextInt(10));
System.out.println("任務一執行完成");
}
public void doTackTwo() throws InterruptedException {
TimeUnit.SECONDS.sleep(random.nextInt(10));
System.out.println("任務二執行完成");
}
public void doTackThree() throws InterruptedException {
TimeUnit.SECONDS.sleep(random.nextInt(10));
System.out.println("任務三執行完成");
}
}
第二步、在Controller類中調用對應的任務類,并且按照順序調用執行任務
@RestController
public class TaskController {
@Autowired
private Task task;
@GetMapping("/hello")
public String hello() throws InterruptedException {
task.doTackOne();
task.doTackTwo();
task.doTackThree();
return "Task";
}
}
執行結果如下,無論如何調用接口,這三個任務都是按照代碼順序執行輸出的。是以這三個任務在這樣的一種執行情況下,他們是同步執行的,也就是說任務一執行完成之後,任務二執行,任務二執行完成之後,才會輪到任務三執行。而這其中,無論任務一需要随機幾秒的等待時間,任務二都必須完成。
異步調用
考慮在上面這種情況下,任務一執行完成的時間是不固定的,也可能1秒執行完成也可能10秒執行完成。如果其中一個任務執行時間較長的話可能會影響到其他任務的執行。其實從邏輯上看,三個任務之間并沒有明确的因果關系,第二個任務的執行并不需要第一個任務執行的傳回結果做為依賴。這個時候。我們就可以考慮采用異步調用的方式了,下面我們就可以将任務類代碼進行修改。
@Component
public class Task {
public static Random random = new Random();
@Async
public void doTackOne() throws InterruptedException {
TimeUnit.SECONDS.sleep(random.nextInt(10));
System.out.println("任務一執行完成");
}
@Async
public void doTackTwo() throws InterruptedException {
TimeUnit.SECONDS.sleep(random.nextInt(10));
System.out.println("任務二執行完成");
}
@Async
public void doTackThree() throws InterruptedException {
TimeUnit.SECONDS.sleep(random.nextInt(10));
System.out.println("任務三執行完成");
}
}
會看到我們在每個執行任務的方法上都加上了@Async 注解,通過這個注解就可以讓我們的方法執行變成異步執行。
當然光有@Async注解是不夠的,我們還要在主啟動類上标注@EnableAsync,注解允許應用程式通過@Async 注解來進行異步執行,當然我們也可以專門寫一個配置類在配置類中添加@EnableAsync注解。
@SpringBootApplication
@EnableAsync
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
調用之後程式立即傳回,并且任務執行的順序也發生了變化。反複調用之後,會發現每次任務的調用順序都會發生變化。原因就是任務執行的三個方法都被設定了異步執行,在程式啟動之後,調用方法不會去關心這三個方法如何執行,隻關心主程式方法如何執行。是以調用之後,就立即會傳回結果。
這裡需要注意的一點就是,被@Async注解标注的方法不能是靜态方法。
這裡就一個新的問題出現了,我們怎麼知道異步調用的方法到底有沒有執行呢?或者是異步調用的方法是什麼時候執行完成的呢?
這個時候我們就需要對任務方法進行改進了,
異步回調
既然要知道結果,那麼我們在任務執行完成之後進行傳回,是以我們就将代碼改成如下這個樣子來實作。
@Component
public class Task {
public static Random random = new Random();
@Async
public Future<String> doTackOne() throws InterruptedException {
TimeUnit.SECONDS.sleep(random.nextInt(10));
return new AsyncResult<>("任務一完成");
}
@Async
public Future<String> doTackTwo() throws InterruptedException {
TimeUnit.SECONDS.sleep(random.nextInt(10));
return new AsyncResult<>("任務二完成");
}
@Async
public Future<String> doTackThree() throws InterruptedException {
TimeUnit.SECONDS.sleep(random.nextInt(10));
return new AsyncResult<>("任務三完成");
}
}
對任務方法完成改造之後,接下來,我們就需要擷取到對應的傳回值并且對傳回值進行判斷是否任務執行完成。這将如何實作呢?
@RestController
public class TaskController {
@Autowired
private Task task;
@GetMapping("/hello")
public String hello() throws InterruptedException {
Future<String> doTackOne = task.doTackOne();
Future<String> doTackTwo = task.doTackTwo();
Future<String> doTackThree = task.doTackThree();
// 設定自旋等待
while (true){
if (doTackOne.isDone()&&doTackTwo.isDone()&&doTackThree.isDone()){
break;
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println("所有任務完成");
return "Task";
}
}
這個時候,我們通過理論計算可以知道,要想三個任務同時完成,那麼它所用的時間應該是任務執行時間最長的哪個任務決定,也就是說如果任務三執行的時間是2秒,任務二執行的時間是3秒,任務一執行的時間是4秒,那麼最終完成所有任務所用的時間應該是4秒左右。而如果采用同步的方式我們可以計算的到完成所有任務的總用時是三者之和,也就是9秒。
從這裡可以知道,采用異步的方式為整個的過程調用節省了5秒中的等待時間。可見異步調用在一些并發項目中确實可以節省不少時間提升接口調用效率。