SpringBoot
(Spring)中使用註解方式進行多線程異步(學習筆記2020.3.30)
前言:
以前的項目實現異步多線程大多是使用代碼配置好線程池進行代碼方式調用。
而現在
Spring
提供了註解方式開啓異步處理。(Annotation
支持調度和異步執行)要啓用對
@Async
註釋的支持,可以將@EnableAsync
添加到其中一個@Configuration
classes 中。
1. 創建一個SpringBoot
項目
1.1 依賴:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>
<artifactId>springboot-asynchronous</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
1.2 在啓動類上啓用異步註解
@SpringBootApplication
@EnableAsync //啓用異步
public class ApplicationAsynchronous {
public static void main(String[] args) {
SpringApplication.run(ApplicationAsynchronous.class,args);
}
}
1.3 創建一個給框架管理的bean
裏面有一個是註解標記的異步方法, 一個是同步方法, 然後進行調用看調用順序與線程名稱。
@Component
public class Asynchronous {
@Async
public void asynchronous1(String name) throws InterruptedException {
Thread.sleep(2000);
System.out.println("異步睡2秒後執行"+name+"線程名稱:"+Thread.currentThread().getName());
}
public void commonMethod1(String name) throws InterruptedException {
Thread.sleep(2000);
System.out.println("普通睡2秒後執行"+name+"線程名稱:"+Thread.currentThread().getName());
}
}
1.4 進行測試
@SpringBootTest
@RunWith(SpringRunner.class)
public class AsynchronousTest {
@Autowired
private Asynchronous asynchronous;
@Test
public void asynchronousTest() throws InterruptedException {
System.out.println("異步方法準備執行前");
asynchronous.asynchronous1("張韶涵1");
System.out.println("異步方法執行後");
asynchronous.commonMethod1("張韶涵2");
System.out.println("同步後方法準備執行後");
}
}
輸出的結果爲: (可以看出異步方法執行了並不用等待返回結果, 在執行下一步)
異步方法準備執行前
異步方法執行後
普通睡2秒後執行張韶涵2線程名稱:main
同步後方法準備執行後
異步睡2秒後執行張韶涵1線程名稱:task-1
因爲異步的原因,程序並沒有被
sleep
方法阻塞,這就是異步調用的好處。同時異步方法內部會新啓一個線程來執行,這裏線程名稱爲task - 1默認情況下的異步線程池配置使得線程不能被重用,每次調用異步方法都會新建一個線程,我們可以自己定義異步線程池來優化。
1.5 修改默認異步線程池application.yml
修改application文件:
spring:
task:
execution:
pool:
max-size: 32 #最大線程數
core-size: 32 #核心線程數, 默認8
keep-alive: 10s #線程最大空閒時間, 默認值60
allow-core-thread-timeout: true #允許核心線程超時 默認值true
queue-capacity: 100 #緩衝隊列大小
thread-name-prefix: mythread- #線程名
shutdown:
await-termination: true #是否等待所有線程執行完畢才關閉線程池,默認值爲false。
await-termination-period: 60 #waitForTasksToCompleteOnShutdown的等待的時長,默認值爲0,即不等待。
輸出的結果爲:
異步方法準備執行前
異步方法執行後
普通睡2秒後執行張韶涵2線程名稱:main
同步後方法準備執行後
異步睡2秒後執行張韶涵1線程名稱:mythread-1
1.6 處理異步回調
如果異步方法具有返回值的話,需要使用
Future
來接收回調值。 修改asynchronous1
方法
@Async
public Future<String> asynchronous1(String name) throws InterruptedException {
Thread.sleep(2000);
System.out.println("異步睡2秒後執行"+name+"線程名稱:"+Thread.currentThread().getName());
return new AsyncResult<>("異步返回結果"+name);
}
運行測試:
@Test //
public void asynchronousTest() throws Exception {
System.out.println("異步方法準備執行前");
String name = asynchronous.asynchronous1("張韶涵1").get();
System.out.println("異步方法執行後"+name);
asynchronous.commonMethod1("張韶涵2");
System.out.println("同步後方法準備執行後");
}
結果: 可以看出調用了
get()
後線程會阻塞到獲取執行結果, 在往下執行。
get
還有一個get(long timeout, TimeUnit unit)
重載方法,我們可以通過這個重載方法設置超時時間,即異步方法在設定時間內沒有返回值的話,直接拋出java.util.concurrent.TimeoutException
異常。
異步方法準備執行前
異步睡2秒後執行張韶涵1線程名稱:mythread-1
異步方法執行後異步返回結果張韶涵1
普通睡2秒後執行張韶涵2線程名稱:main
同步後方法準備執行後
比如: 設置10秒後沒有返回結果叫拋出異常!
String name = asynchronous.asynchronous1("張韶涵1").get(10, TimeUnit.SECONDS);
擴展資料:
使用配置類方式自定義配置線程池:
@Configuration
public class AsyncPoolConfig {
@Bean
public ThreadPoolTaskExecutor asyncThreadPoolTaskExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(200);
executor.setQueueCapacity(25);
executor.setKeepAliveSeconds(200);
executor.setThreadNamePrefix("mythread-");
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
//當沒有線程可以被使用時的處理策略(拒絕任務),默認策略爲abortPolicy
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.initialize();
return executor;
}
}
CallerRunsPolicy
:用於被拒絕任務的處理程序,它直接在 execute 方法的調用線程中運行被拒絕的任務;如果執行程序已關閉,則會丟棄該任務。AbortPolicy
:直接拋出java.util.concurrent.RejectedExecutionException
異常。DiscardOldestPolicy
:當線程池中的數量等於最大線程數時、拋棄線程池中最後一個要執行的任務,並執行新傳入的任務。DiscardPolicy
:當線程池中的數量等於最大線程數時,不做任何動作。
要使用該線程池,只需要在
@Async
註解上指定線程池Bean名稱即可:
@Async("asyncThreadPoolTaskExecutor")
public Future<String> asynchronous1(String name) throws InterruptedException {
Thread.sleep(2000);
System.out.println("異步睡2秒後執行"+name+"線程名稱:"+Thread.currentThread().getName());
return new AsyncResult<>("異步返回結果"+name);
}
1