在SpringBoot中要實現非同步有很多種方式,比如說你自己手寫一個ThreadPool、或是藉由外部的RabbitMQ都可以實現非同步的方法。
那這邊我選擇實做的方法是使用SpringBoot提供的@Async,什麼是非同步呢?
舉個例子假設我在做櫃檯,現在有10個客人要辦帳戶,每個客人過來我這邊,都要寫10分鐘的表格,寫完後我在幫他們登記,花兩分鐘。如果一個客人一個客人這樣過來,我需要花120分鐘才可以消化掉。但如果我可以先把表格發給全部10個人填,這樣我只要花10+10*2=30分鐘就可以。
這就是非同步操作,簡而言之就是避免無意義的等待。
由於網路上寫的教學實在都太複雜了,我想寫一篇最最最基本的那一種,不需要配什麼config,連什麼資料庫,一切都是最基本的樣子
Github連結
https://github.com/Hoxton019030/SpringBootAsync
如何創建一個非同步方法
非常簡單!只要在方法上加上@Async的註解就可以囉!就像這樣
1
2
3
4
5
6
7
8
9
| @Async
public void testAsync() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("非同步測試執行,執行緒為:" + Thread.currentThread().getName());
}
|
實際感受何謂非同步執行
我們可以寫一個controller來呼叫我們剛剛寫好的testAsync()
1
2
3
4
5
6
7
| @GetMapping("/")
public String testAsync() {
System.out.println("開始測試,執行緒為 - " + Thread.currentThread().getName());
asyncTask.testAsync(); //這東西會印出非同步測試執行,執行緒為:Async-Service-1
System.out.println("測試結束,執行緒為 - " + Thread.currentThread().getName());
return "非同步測試";
}
|
專案執行完後,可以輸入
http://localhost:8080/
訪問endPoint,可以看到"非同步測試執行,執行緒為:
這句話打印出來的時間點,遠小於測試結束,執行緒為 -
這句話,這就是非同步執行帶來的好處。
繼續感受非同步執行
之前在資策會的專案中,有一個功能是要寄出Email的,當時量沒那麼大,所以沒感覺,實際工作之後發現寄Email這件事情要花很多時間在等待上面,這就是一個很適合用非同步去處理的事情。在這邊我一樣使用Thread.sleep()去模擬寄信這件事情
1
2
3
4
5
6
7
8
9
10
11
12
13
| @Async
public void sendEmail(int mailNumber) {
for (int i = 1; i < mailNumber + 1; i++) {
try {
Thread.sleep(1000);
System.out.println("寄出第" + i + "封信,所使用的執行緒是" + Thread.currentThread().getName());
} catch (InterruptedException e) {
System.out.println("寄出第" + i + "封信時發生錯誤!");
throw new RuntimeException(e);
}
}
}
|
1
2
3
4
5
6
7
8
| @GetMapping("/email")
public String sendEmail() {
System.out.println("開始測試,執行緒為 - " + Thread.currentThread().getName());
asyncTask.sendEmail(10);
System.out.println("測試結束,執行緒為 - " + Thread.currentThread().getName());
return "非同步寄出Email";
}
|
可以在專案中輸入
http://localhost:8080/email
來感受到非同步的感覺
非同步Executor
有時候,有時候,我們在寫的時候不能知道這東西未來會希望變成一個非同步方法,或是說希望可以用非同步的方式在程式內部實現一些功能,像這種時候我們可以結合Java的functional Interface來實現非同步的操作
首先先寫一個方法介面
1
2
3
4
5
6
7
8
9
| /**
* 其實就是方法介面啦>/////<
*/
@FunctionalInterface
public interface ExecuteInterface {
void execute();
}
|
接著完成AsyncTask的方法
1
2
3
4
| @Async
public void asyncExecutor(ExecuteInterface executeInterface) {
executeInterface.execute();
}
|
這樣就完成了喔!有沒有,很快吧!
當我們要在專案中非同步執行某些事情時,就可以使用這個asyncExecutor來做事了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| @GetMapping("/executor")
public String executeAsyncTask(){
asyncTask.asyncExecutor(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("我在做非同步工作耶!");
});
return "非同步執行器";
}
|