SpringBoot中@Async非同步的使用及非同步與同步的區別
簡介
在開發過程中,非同步是提升系統併發能力的一個重要利器。而 spring 中的 @Async 非同步註解,使我們能夠非常方便地實現方法地非同步呼叫。接下來主要結合以下幾個問題來講述 java 程式中的非同步的使用:
- 什麼是同步
- 什麼是非同步,以及非同步的作用
- 如何在 SpringBoot 中使用非同步
1、什麼是同步
同步呼叫,是遵循的順序處理,一個一個呼叫
特徵:在呼叫多個方法的時候,強調先後順序,執行完一個方法再執行下一個方法,方法與方法之間不可調換先後順序,不會並行執行
示例:
package com.example.demo.test1; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import java.util.concurrent.Future; @Component @Slf4j public class SyncDemo { public void test1() throws Exception { log.info("test1開始執行"); Long start = System.currentTimeMillis(); Thread.sleep(1000); Long end = System.currentTimeMillis(); log.info("test1執行結束"); log.info("test1執行時長:{}毫秒", end-start); } public void test2() throws Exception { log.info("test2開始執行"); Long start = System.currentTimeMillis(); Thread.sleep(1500); Long end = System.currentTimeMillis(); log.info("test2執行結束"); log.info("test2執行時長:{}毫秒", end-start); } public void test3() throws Exception { log.info("test3開始執行"); Long start = System.currentTimeMillis(); Thread.sleep(2000); Long end = System.currentTimeMillis(); log.info("test3執行結束"); log.info("test3執行時長:{}毫秒", end-start); } public static void main(String[] args) throws Exception { SyncDemo syncDemo = new SyncDemo(); Long start = System.currentTimeMillis(); syncDemo.test1(); syncDemo.test2(); syncDemo.test3(); Long end = System.currentTimeMillis(); log.info("總執行時長:{}毫秒", end-start); } }
執行結果:
總執行時長,是test1(),test2(),test3() 三個方法的近似總和
2、什麼是非同步
非同步主要是呼叫方法後,不用等方法執行完畢就直接返回。非同步方法會與其他方法並行執行,以此來節約時間,提高程式的併發能力。
示例:
為準確統計所有非同步方法執行時間,現在將方法的返回物件,改為 Future,以此來可以判斷方法是否執行完畢
示例程式碼如下:
IAsyncDemoService.java
package com.example.demo.test1; import java.util.concurrent.Future; public interface IAsyncDemoService { public Future<String> test1() throws Exception ; public Future<String> test2() throws Exception ; public Future<String> test3() throws Exception ; }
AsyncDemoServiceImpl.java
package com.example.demo.test1; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.AsyncResult; import org.springframework.stereotype.Service; import java.util.concurrent.Future; @Service @Slf4j public class AsyncDemoServiceImpl implements IAsyncDemoService { @Async @Override public Future<String> test1() throws Exception { log.info("test1開始執行"); Long start = System.currentTimeMillis(); Thread.sleep(1000); Long end = System.currentTimeMillis(); log.info("test1執行結束"); log.info("test1執行時長:{}毫秒", end-start); return new AsyncResult<>("test1完成"); } @Async @Override public Future<String> test2() throws Exception { log.info("test2開始執行"); Long start = System.currentTimeMillis(); Thread.sleep(1500); Long end = System.currentTimeMillis(); log.info("test2執行結束"); log.info("test2執行時長:{}毫秒", end-start); return new AsyncResult<>("test2完成"); } @Async @Override public Future<String> test3() throws Exception { log.info("test3開始執行"); Long start = System.currentTimeMillis(); Thread.sleep(2000); Long end = System.currentTimeMillis(); log.info("test3執行結束"); log.info("test3執行時長:{}毫秒", end-start); return new AsyncResult<>("test3完成"); } }
DemoApplicationTests.java
package com.example.demo;
import com.example.demo.test1.IAsyncDemoService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.Future;
@SpringBootTest
@Slf4j
class DemoApplicationTests {
@Autowired
IAsyncDemoService asyncDemoService;
@Test
void contextLoads() throws Exception{
Long start = System.currentTimeMillis();
Future task1 = asyncDemoService.test1();
Future task2 = asyncDemoService.test2();
Future task3 = asyncDemoService.test3();
while (true) {
if (task1.isDone() && task2.isDone() && task3.isDone()) {
break;
}
}
Long end = System.currentTimeMillis();
log.info("總執行時長:{}毫秒", end-start);
}
}
執行結果如下:總執行時間與執行最長的 test3()方法的時間相近
結論:非同步方法是通過形式,呼叫後即返回,具體方法的執行,不影響其他方法的執行,多個方法並行的方式實現。
3、@Async 失效的情況
在使用 註解@Async 的時候,一定要注意規避以下情況,會造成 註解失效的情況
-
註解@Async的方法不是public方法
-
註解@Async的返回值只能為void或Future
-
註解@Async方法使用static修飾也會失效
-
spring無法掃描到非同步類,沒加註解@Async或@EnableAsync註解
-
呼叫方與被呼叫方不能在同一個類
-
類中需要使用@Autowired或@Resource等註解自動注入,不能自己手動new物件
-
在Async方法上標註@Transactional是沒用的.但在Async方法呼叫的方法上標註@Transcational是有效的
4、非同步執行緒池的設定
使用 @Async 來實現非同步,預設使用Spring建立ThreadPoolTaskExecutor,預設執行緒池的引數設定為:
-
預設心執行緒數:8,
-
最大執行緒數:Integet.MAX_VALUE,
-
佇列使用LinkedBlockingQueue,
-
容量是:Integet.MAX_VALUE,
-
空閒執行緒保留時間:60s,
-
執行緒池拒絕策略:AbortPolicy。
問題:_
_併發情況下,會無限建立執行緒,導致系統資源耗盡,所以我們要手動設定執行緒池的配置,來避免無限建立執行緒的情況。
配置如下:
application.yaml
spring:
task:
execution:
pool:
max-size: 6
core-size: 3
keep-alive: 3s
queue-capacity: 1000
自定義執行緒池配置類:
ExecutorConfig.java
@Configuration
@Data
public class ExecutorConfig{
/**
* 核心執行緒
*/
private int corePoolSize;
/**
* 最大執行緒
*/
private int maxPoolSize;
/**
* 佇列容量
*/
private int queueCapacity;
/**
* 保持時間
*/
private int keepAliveSeconds;
/**
* 名稱字首
*/
private String preFix;
@Bean
public Executor myExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(corePoolSize);
executor.setMaxPoolSize(maxPoolSize);
executor.setQueueCapacity(queueCapacity);
executor.setKeepAliveSeconds(keepAliveSeconds);
executor.setThreadNamePrefix(preFix);
executor.setRejectedExecutionHandler( new ThreadPoolExecutor.AbortPolicy());
executor.initialize();
return executor;
}
}
使用方式:
在使用 @Async 的時候,指定執行緒池為自定義執行緒池 myExecutor
例如:
@Async("myExecutor")
public void test1() throws Exception {
}