Spring Boot系列之@Async異步調用

寫在前面的話

哈嘍,好久不見,你們還好嗎?

今天給大家帶來的是我在實際項目上遇到的一個問題。

流程大致是,調用接口,然後將接口返回的數據更新一份到本地數據庫,然後返回給前端。更新到本地數據庫這個操作原本是用的異步。

國慶回老家,公司打電話來,前端轉幾秒的圈圈,然後無數據。經查,是Redis出了問題,用不了。

什麼意思?

從接口請求到的數據,更新到本地數據庫,這裏有一個策略,先將數據放到Redis中,然後進行對比,如果不一致,再更新。Redis不可用,那麼都查詢數據庫,就會很慢,前端請求接口一般是5s超時。

如果是異步,也就不會出現這個問題了。

所以,我們就先看看當時,我的代碼明明是異步的,爲什麼沒有生效呢?

@Async無效

先看一個例子。

Controller代碼如下:

<pre mdtype="fences" cid="n31" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@GetMapping("/invalid")
public String invalidAsyncExample() {
iTestAsyncService.invalidAsyncExample();
return "測試完成 " + LocalDateTime.now().toString();
}</pre>

Service代碼如下:

<pre mdtype="fences" cid="n35" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Override
public void invalidAsyncExample() {
log.info("流程-1-{}", Thread.currentThread().getId());
invalidAsyncTask();
log.info("流程-3-{}", Thread.currentThread().getId());
}</pre>

Async代碼如下:

<pre mdtype="fences" cid="n43" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Async
public void invalidAsyncTask() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("流程-2-{}", Thread.currentThread().getId());
}</pre>

執行結果:

<pre mdtype="fences" cid="n49" lang="" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">2020-11-11 21:14:06.784 INFO 13592 --- [nio-8080-exec-1] c.f.s.a.s.impl.TestAsyncServiceImpl : 流程-1-125
2020-11-11 21:14:08.785 INFO 13592 --- [nio-8080-exec-1] c.f.s.a.s.impl.TestAsyncServiceImpl : 流程-2-125
2020-11-11 21:14:08.785 INFO 13592 --- [nio-8080-exec-1] c.f.s.a.s.impl.TestAsyncServiceImpl : 流程-3-125</pre>

結果分析:確實是同步執行,沒什麼明明加了@Async的,異步沒生效呢?

帶這個這樣一個疑問,在百度上尋找答案。同時,也決定閱讀這一塊的源碼。想看一下,這個異步到底是怎麼實現的。

通過閱讀源碼,會發現,Spring默認是用代理實現異步的。

什麼意思?

你可以這樣理解,你調用的類需要Spring幫你代理,然後才能異步去執行。

上面的示例代碼,invalidAsyncTask(); 調用的方法很明確,不需要代理,這時候Spring也就不能幫你異步去執行了。

關於源碼分析,後面在寫源碼博文的時候,再來。

無返回值的異步任務

首先呢,需要 @EnableAsync

Controller:

<pre mdtype="fences" cid="n105" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@GetMapping("/no-value")
public String noValueAsyncExample() {
iTestAsyncService.noValueAsyncExample();
return "測試完成 " + LocalDateTime.now().toString();
}</pre>

Service:

<pre mdtype="fences" cid="n111" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Override
public void noValueAsyncExample() {
log.info("流程-1-{}", Thread.currentThread().getId());
iAsyncService.exampleTask();
log.info("流程-3-{}", Thread.currentThread().getId());
}</pre>

耗時任務:

<pre mdtype="fences" cid="n117" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Override
public void exampleTask() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("耗時任務-2-{}", Thread.currentThread().getId());
}</pre>

Async:

<pre mdtype="fences" cid="n123" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Override
@Async
public void exampleTask() {
iTestAsyncService.exampleTask();
}</pre>

這裏要注意,因爲我們把耗時的任務放在同一個service裏面,所以就會產生循環依賴的問題,需要用到 @Lazy

測試結果:

<pre mdtype="fences" cid="n134" lang="" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">2020-11-11 22:32:50.019 INFO 18888 --- [nio-8080-exec-7] c.f.s.a.s.impl.TestAsyncServiceImpl : 流程-1-131
2020-11-11 22:32:50.020 INFO 18888 --- [nio-8080-exec-7] c.f.s.a.s.impl.TestAsyncServiceImpl : 流程-3-131
2020-11-11 22:32:52.021 INFO 18888 --- [ task-9] c.f.s.a.s.impl.TestAsyncServiceImpl : 耗時任務-2-152</pre>

有返回值的異步任務

也是需要 @EnableAsync

Controller:

<pre mdtype="fences" cid="n148" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@GetMapping("/value")
public int valueAsyncExample() {
return iTestAsyncService.valueAsyncExample();
}</pre>

Service:

<pre mdtype="fences" cid="n156" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Override
public int valueAsyncExample() {
int result = 0;

long startTime = System.currentTimeMillis();

List<Future<Integer>> futureList = new ArrayList<>();

for (int i = 0; i < 10; i++) {
Future<Integer> future = iAsyncService.addTask(i);
futureList.add(future);
}

for (Future<Integer> f : futureList) {
Integer value = null;
try {
value = f.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
if (value != null)
result += value;
}

long endTime = System.currentTimeMillis();

log.info("耗時 {} s", (endTime - startTime) / 1000D);

return result;
}</pre>

任務:

<pre mdtype="fences" cid="n162" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Override
public Future<Integer> addTask(int n) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("計算任務-{}", Thread.currentThread().getId());
return AsyncResult.forValue(n + 2);
}</pre>

Async:

<pre mdtype="fences" cid="n168" lang="java" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">@Override
@Async
public Future<Integer> addTask(int i) {
return iTestAsyncService.addTask(i);
}</pre>

同樣,這裏要注意,因爲我們把耗時的任務放在同一個service裏面,所以就會產生循環依賴的問題,需要用到 @Lazy

測試結果:

<pre mdtype="fences" cid="n178" lang="" class="md-fences md-end-block ty-contain-cm modeLoaded" spellcheck="false" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 15px; margin-top: 15px; width: inherit; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">2020-11-11 22:27:05.152 INFO 18888 --- [ task-3] c.f.s.a.s.impl.TestAsyncServiceImpl : 計算任務-146
2020-11-11 22:27:05.152 INFO 18888 --- [ task-5] c.f.s.a.s.impl.TestAsyncServiceImpl : 計算任務-148
2020-11-11 22:27:05.152 INFO 18888 --- [ task-4] c.f.s.a.s.impl.TestAsyncServiceImpl : 計算任務-147
2020-11-11 22:27:05.152 INFO 18888 --- [ task-6] c.f.s.a.s.impl.TestAsyncServiceImpl : 計算任務-149
2020-11-11 22:27:05.153 INFO 18888 --- [ task-7] c.f.s.a.s.impl.TestAsyncServiceImpl : 計算任務-150
2020-11-11 22:27:05.152 INFO 18888 --- [ task-2] c.f.s.a.s.impl.TestAsyncServiceImpl : 計算任務-145
2020-11-11 22:27:05.153 INFO 18888 --- [ task-8] c.f.s.a.s.impl.TestAsyncServiceImpl : 計算任務-151
2020-11-11 22:27:05.152 INFO 18888 --- [ task-1] c.f.s.a.s.impl.TestAsyncServiceImpl : 計算任務-144
2020-11-11 22:27:07.154 INFO 18888 --- [ task-6] c.f.s.a.s.impl.TestAsyncServiceImpl : 計算任務-149
2020-11-11 22:27:07.154 INFO 18888 --- [ task-3] c.f.s.a.s.impl.TestAsyncServiceImpl : 計算任務-146
2020-11-11 22:27:07.154 INFO 18888 --- [nio-8080-exec-1] c.f.s.a.s.impl.TestAsyncServiceImpl : 耗時 4.006 s</pre>

頁面結果

<pre spellcheck="false" class="md-fences md-end-block ty-contain-cm modeLoaded" lang="" cid="n185" mdtype="fences" style="box-sizing: border-box; overflow: visible; font-family: var(--monospace); font-size: 0.9em; display: block; break-inside: avoid; text-align: left; white-space: normal; background-image: inherit; background-position: inherit; background-size: inherit; background-repeat: inherit; background-attachment: inherit; background-origin: inherit; background-clip: inherit; background-color: rgb(248, 248, 248); position: relative !important; border: 1px solid rgb(231, 234, 237); border-radius: 3px; padding: 8px 4px 6px; margin-bottom: 0px; margin-top: 0px; width: inherit;">65</pre>

測試代碼

https://github.com/fengwenyi/study-spring-boot/tree/master/spring-boot-async

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章