如何在項目中使用Spring異步調用注解@Async


本文主要介紹如何使用Spring框架提供的異步調用注解@Async,異步線程池配置、異常捕獲處理。

開啟@Async注解支持

使用@Async注解的之前,必須在項目中啟動時調用@EnableAsync注解。比如通過定義一個JavaConfig文件:

@Configuration
@EnableAsync
public class AsyncConfig  {

}

異步調用

使用@Async異步執行無返回值的任務

定義一個任務類AsyncTask,包含兩個執行耗時任務的方法task1()、task2(),在兩個方法上添加@Async

@Service
@Slf4j
public class AsyncTask {

    @Async
    public void task1() {
        log.info("task1 start");
    }

    @Async
    public void task2() {
        log.info("task2 start");
    }
}

定義測試類,串行調用AsyncTask.task1()和AsyncTask.task2()

@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class AsyncTaskTest {

    @Autowired
    private AsyncTask asyncTask;

    @Test
    public void taskTest() {
        log.info("taskTest start");
        asyncTask.task1();
        asyncTask.task2();
        log.info("taskTest end");
    }
}

從運行結果中看,task1和task2分別在兩個不同的線程中執行:

INFO [15:18:29.182][main][com.breezek.demo.common.AsyncTaskTest][25]:taskTest start
INFO [15:18:29.188][main][com.breezek.demo.common.AsyncTaskTest][29]:taskTest end
INFO [15:18:29.192][task-1][com.breezek.demo.common.AsyncTask][29]:task2 start
INFO [15:18:29.192][task-2][com.breezek.demo.common.AsyncTask][24]:task1 start

異步回調

使用@Async異步執行有返回值的任務,並獲取任務執行結果。

定義AsyncTask類,創建兩個帶返回值的異步方法,返回值類型為Future ,task1執行時間5s,task2執行時間10s,在兩個方法上添加@Async

@Service
@Slf4j
public class AsyncTask {

    @Async
    public Future<String> task1() throws InterruptedException {
        log.info("task1 start");
        Thread.sleep(5000L);
        log.info("task1 end");
        return new AsyncResult<>("task1 result");
    }

    @Async
    public Future<Integer> task2() throws InterruptedException {
        Integer abc = 1;
        log.info("task2 start");
        Thread.sleep(10000L);
        log.info("task2 end");
        return new AsyncResult<>(abc);
    }
}

定義測試類,分別調用task1、task2,並等待task1和task2執行完畢

@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class AsyncTaskTest {

    @Autowired
    private AsyncTask asyncTask;

    @Test
    public void taskTest() throws InterruptedException {
        log.info("taskTest start");
        Future<String> task1Future = asyncTask.task1();
        Future<Integer> task2Future = asyncTask.task2();
        // do something
        for (int i = 0; i < 1000; i++) {
            
        }
        while (!task1Future.isDone() || !task2Future.isDone()) {
        }
        log.info("taskTest end");
    }
}

運行結果:

INFO [17:54:24.554][main][com.breezek.demo.common.AsyncTaskTest][28]:taskTest start
INFO [17:54:24.566][task-1][com.breezek.demo.common.AsyncTask][27]:task1 start
INFO [17:54:24.566][task-2][com.breezek.demo.common.AsyncTask][36]:task2 start
INFO [17:54:29.569][task-1][com.breezek.demo.common.AsyncTask][29]:task1 end
INFO [17:54:34.570][task-2][com.breezek.demo.common.AsyncTask][38]:task2 end
INFO [17:54:34.570][main][com.breezek.demo.common.AsyncTaskTest][34]:taskTest end

可以看出來,main線程等待兩個子線程執行完畢后再繼續向下運行

使用@Async注解時,需要注意以下幾點,否則異步調用不會生效:

  • 異步方法不能定義為static類型
  • 調用方法和異步方法不能定義在同一個類中

AsyncConfigurer配置

下面的代碼中是如何配置異步調用使用的線程池、void返回值異常捕獲處理

AsyncConfigurer接口是Spring提供的,我們定義JavaConfig時實現它:

@Configuration
@EnableAsync
@Slf4j
public class AsyncConfig implements AsyncConfigurer {
    
    /**
     * 配置線程池,減少在調用每個異步方法時創建和銷毀線程所需的時間
     */
    @Override
    public Executor getAsyncExecutor() {
        // 初始化Spring框架提供的線程池
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心線程數
        executor.setCorePoolSize(10);
        // 最大線程數
        executor.setMaxPoolSize(20);
        // 任務等待隊列大小
        executor.setQueueCapacity(10);
        // 任務拒絕策略,如果線程池拒絕接受任務,使用調用線程執行
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 定義線程名稱前綴
        executor.setThreadNamePrefix("async-executor-");
        // 調用線程池初始化方法,如果在getAsyncExecutor()加上了@Bean注解,這個方法可以不調用,因為ThreadPoolTaskExecutor實現了InitializingBean接口,Spring在初始化Bean時會調用InitializingBean.afterPropertiesSet()
        executor.initialize();
        return executor;
    }

    /**
     * void返回值異步方法異常捕獲處理
     */
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new AsyncExceptionHandler();
    }

    /**
     * 異常捕獲處理類
     */
    public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {

        @Override
        public void handleUncaughtException(Throwable ex, Method method, Object... params) {
            log.error(String.format("Async method: %s has uncaught exception, params: %s.", method, JSON.toJSONString(params)), ex);
        }
    }
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM