大多數時候處理業務都是以同步的方式來實現的。但在有些特殊的場景中,需要用異步的方式來實現。
1、最原始的實現異步的方式:單獨起一個線程。缺點在於:異步處理業務太多時,同時運行的線程太多,可能導致服務器崩潰。
2、然后,出現了線程池,線程池對線程數量進行控制和對線程進行復用,解決了上面的問題。
3、在2中,程序員需要自己編寫一個線程,然后交給線程池管理。spring為了簡化程序員的代碼,內置了@Async注解,程序員只需編寫純業務代碼和設置自己的線程池即可。
基於@Async標注的⽅法,稱之為異步⽅法;這些⽅法將在執⾏的時候,將會在獨⽴的線程中被執⾏,調⽤者⽆需等待它的完成,即可繼續其他的操作。
如何使用:
1、在應用中用@EnableAsync注解來啟用@Aysnc注解。可以在spring⼊⼝類加,也可以在需要調⽤異步⽅法的類上加,也可以在應用配置類上加。
2、在需要異步的方法上加@Aysnc。如果加在類上,相當於這個類的所有方法都加了@Aysnc,那么這個類所有的⽅法都是異步執⾏的。
public class AysncTask { /** * taskA()被調用時,該方法會被異步執行:會將任務交給線程池去完成 */ @Async public void taskA() { System.out.println("AAAAAAAA"); } public void taskB() { System.out.println("BBBBBBBBBBB"); } }
@Async public class AysncTask { /** * taskA()被調用時,該方法會被異步執行:會將任務交給線程池去完成 */ public void taskA() { System.out.println("AAAAAAAA"); } /** * taskB()被調用時,該方法會被異步執行:會將任務交給線程池去完成 */ public void taskB() { System.out.println("BBBBBBBBBBB"); } }
3、如果在@Aysnc中沒有指定線程池,會默認使用spring提供的默認線程池SimpleAsyncTaskExecutor。
缺點:線程池為每個任務都單獨創建一個線程,不會重用線程。這樣,就跟最原始實現異步的方式有了相同的缺點:異步處理業務太多時,同時運行的線程太多,可能導致服務器崩潰。
因此,我們一定要創建自己的線程池,並給異步任務指定到哪個線程池去執行。
創建線程池:
@Configuration public class ExecutorConfig { @Bean public Executor asyncServiceExecutor() { log.info("開始線程池:asyncServiceExecutor"); ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //配置核心線程數 executor.setCorePoolSize(50); //配置最大線程數 executor.setMaxPoolSize(100); //配置隊列大小 executor.setQueueCapacity(99999); //配置線程池中的線程的名稱前綴 executor.setThreadNamePrefix("async-service-"); // 設置拒絕策略:當pool已經達到max size的時候,如何處理新任務 // CALLER_RUNS:不在新線程中執行任務,而是有調用者所在的線程來執行 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); //執行初始化 executor.initialize(); return executor; } }
為任務指定線程池:
public class AysncTask { /** * taskA()被調用時,該方法會被異步執行:會將任務交給線程池去完成 */ @Async("asyncServiceExecutor") public void taskA() { System.out.println("AAAAAAAA"); } public void taskB() { System.out.println("BBBBBBBBBBB"); } }
使用過程中的其他注意點:
1、@Aysnc修飾的異步⽅法不能和調⽤此⽅法的方法放在⼀個類⾥⾯。
如:@Aysnc修改的方法A(),在方法B()里調用方法A()。B()和A()不能在同一個類里。
2、@Aysnc修飾的異步⽅法上加@Transactional將無法產生事務管理。因為異步方法單獨開了一個線程導致的。
解決辦法:將需要做事務管理的代碼單獨寫成一個方法,加上@Transactional,然后在異步調用方法中調用這個方法。
3、可以為異步方法設置返回值,但必須是java.util.concurrent.Future類型,否則接收不到。如:Future的實現類:AsyncResult、CompletableFuture。