天天看點

SpringBoot使用@Async的總結

作者:在天上飛的的程式員
SpringBoot使用@Async的總結

一些業務場景我們需要使用多線程異步執行任務,加快任務執行速度。

之前有寫過一篇文章叫做: 異步程式設計利器:CompletableFuture

在實際工作中也更加推薦使用CompletableFuture,因為它實作異步方式更加優雅,而且功能更加強大!

既然SpringBoot能通過 @Async 也實作異步執行任務,那麼這篇文章就來總結下如何使用 @Async 實作異步執行任務。

一、SpringBoot使用@Async注解步驟

1、啟動類上使用@EnableAsync注解

@SpringBootApplication
@EnableAsync
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
           

2、異步方法所在的類注入容器中

@Componet
 public class Test{

}
           

除了@Componet,也可以是@Controller、@RestController、@Service、@Configuration等注解,加入到Ioc容器裡。

3、方法上加上@Async注解

@Service
 public class Test{

	@Async
	public void a() {

	}
}
           

二、哪些情況會導緻@Async異步失效?

如果你明明是按照上面的步驟來的,但是發現@Async注解還是不起作用,這裡還有兩點注意,因為@Async是基于Aop思想實作的,所有下面兩種情況也會失效。

1、異步方法使用static修飾

@Async
    public static void a() {

    }
           

2、調用方法和異步方法在同一個類中

當異步方法和調用方法在同一個類中時,是沒辦法通過Ioc裡的bean來執行異步方法的,進而變成同步方法。

如下:

@Component
public class Task {

    /**
     * 調異步方法和異步方法在同一個類 @Async執行失敗
     */
    public void dotask() {
        this.taskOne();
        this.taskTwo();
    }

    @Async
    public void taskOne() {
        //執行任務1
    }
    
    @Async
    public void taskTwo() {
        //執行任務2
    }
}
           

三、SpringBoot結合@Async實作異步示例

首先我們來看同步方法

1、同步調用示例

@Component
@Slf4j
public class DemoTask {
    
    public void taskOne() throws Exception {
        log.info("===執行任務1===");
        long start = System.currentTimeMillis();
        Thread.sleep(200);
        long end = System.currentTimeMillis();
        log.info("任務1執行結束,總耗時={} 毫秒", end - start);
    }

    public void taskTwo() throws Exception {
        log.info("===執行任務2===");
        long start = System.currentTimeMillis();
        Thread.sleep(200);
        long end = System.currentTimeMillis();
        log.info("任務2執行結束,總耗時={} 毫秒", end - start);
    }

    public void taskThere() throws Exception {
        log.info("===執行任務3===");
        long start = System.currentTimeMillis();
        Thread.sleep(200);
        long end = System.currentTimeMillis();
        log.info("任務3執行結束,總耗時={} 毫秒", end - start);
    }
}

           

執行方法

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

    @Autowired
    private DemoTask demoTask;
    
    @Test
    public void runDemo() throws Exception {
        long start = System.currentTimeMillis();
        demoTask.taskOne();
        demoTask.taskTwo();
        demoTask.taskThere();
        long end = System.currentTimeMillis();
        log.info("總任務執行結束,總耗時={} 毫秒", end - start);
    }
}
           

輸出日志

===執行任務1===
任務1執行結束,總耗時=204 毫秒
===執行任務2===
任務2執行結束,總耗時=203 毫秒
===執行任務3===
任務3執行結束,總耗時=201 毫秒
總任務執行結束,總耗時=613 毫秒
           

2、異步調用示例

異步方法

@Component
@Slf4j
public class AsyncTask {

    @Async
    public void taskOne() throws Exception {
      //執行内容同上,省略
    }

    @Async
    public void taskTwo() throws Exception {
      //執行内容同上,省略
    }

    @Async
    public void taskThere() throws Exception {
       //執行内容同上,省略
    }
}
           

調用方法

@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@EnableAsync
@SpringBootTest
public class AsyncTest {

    @Autowired
    private AsyncTask asyncTask;

    @Test
    public void runAsync() throws Exception {
        long start = System.currentTimeMillis();
        asyncTask.taskOne();
        asyncTask.taskTwo();
        asyncTask.taskThere();
        Thread.sleep(200);
        long end = System.currentTimeMillis();
        log.info("總任務執行結束,總耗時={} 毫秒", end - start);
    }
}    
           

檢視日志

===執行任務1===
===執行任務3===
===執行任務2===
總任務執行結束,總耗時=206 毫秒
任務1執行結束,總耗時=200 毫秒
任務2執行結束,總耗時=201 毫秒
任務3執行結束,總耗時=201 毫秒
           

通過日志可以看出已經是已經實作異步處理任務了,而且異步任務哪個先執行是不确定的。

3、Future異步回調

如果我想異步執行,同時想擷取所有異步執行的結果,那麼這個時候就需要采用Future。

異步方法

@Component
@Slf4j
public class FutureTask {
    
    @Async
    public Future<String> taskOne() throws Exception {
        //執行内容同上,省略
        return new AsyncResult<>("1完成");
    }
    @Async
    public Future<String> taskTwo() throws Exception {
        //執行内容同上,省略
        return new AsyncResult<>("2完成");
    }

    @Async
    public Future<String> taskThere() throws Exception {
        //執行内容同上,省略
        return new AsyncResult<>("執行任務3完成");
    }
}
           

調用方法

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
@EnableAsync
public class FutureTaskTest {

    @Autowired
    private FutureTask futureTask;

    @Test
    public void runAsync() throws Exception {
        long start = System.currentTimeMillis();
        Future<String> taskOne = futureTask.taskOne();
        Future<String> taskTwo = futureTask.taskTwo();
        Future<String> taskThere = futureTask.taskThere();

        while (true) {
            if (taskOne.isDone() && taskTwo.isDone() && taskThere.isDone()) {
                log.info("任務1傳回結果={},任務2傳回結果={},任務3傳回結果={},", taskOne.get(), taskTwo.get(), taskThere.get());
                break;
            }
        }
        long end = System.currentTimeMillis();
        log.info("總任務執行結束,總耗時={} 毫秒", end - start);
    }
}
           

檢視日志

===執行任務2===
===執行任務3===
===執行任務1===
任務1執行結束,總耗時=201 毫秒
任務3執行結束,總耗時=201 毫秒
任務2執行結束,總耗時=201 毫秒
任務1傳回結果=1完成,任務2傳回結果=2完成,任務3傳回結果=執行任務3完成,
總任務執行結束,總耗時=223 毫秒
           

從日志可以看出 異步任務的執行結果都有擷取。

四、@Async+自定義線程池實作異步任務

如果不自定義異步方法的線程池預設使用SimpleAsyncTaskExecutor線程池。

SimpleAsyncTaskExecutor:不是真的線程池,這個類不重用線程,每次調用都會建立一個新的線程。并發大的時候會産生嚴重的性能問題。

Spring也更加推薦我們開發者使用ThreadPoolTaskExecutor類來建立線程池。

自定義線程池

@Configuration
public class ExecutorAsyncConfig {
    
    @Bean(name = "newAsyncExecutor")
    public Executor newAsync() {
        ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
        //設定核心線程數
        taskExecutor.setCorePoolSize(10);
        // 線程池維護線程的最大數量,隻有在緩沖隊列滿了以後才會申請超過核心線程數的線程
        taskExecutor.setMaxPoolSize(100);
        //緩存隊列
        taskExecutor.setQueueCapacity(50);
        //允許的空閑時間,當超過了核心線程數之外的線程在空閑時間到達之後會被銷毀
        taskExecutor.setKeepAliveSeconds(200);
        //異步方法内部線程名稱
        taskExecutor.setThreadNamePrefix("my-xiaoxiao-AsyncExecutor-");
        //拒絕政策
        taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        taskExecutor.initialize();
        return taskExecutor;
    }
}
           

示例代碼

任務1和任務2配置走我們自定義的線程池,任務3還是走預設線程池。

@Component
@Slf4j
public class FutureExecutorTask {

    @Async("newAsyncExecutor")
    public Future<String> taskOne() throws Exception {
        log.info("任務1線程名稱 = {}", Thread.currentThread().getName());
        return new AsyncResult<>("1完成");
    }
    @Async("newAsyncExecutor")
    public Future<String> taskTwo() throws Exception {
        log.info("任務2線程名稱 = {}", Thread.currentThread().getName());
        return new AsyncResult<>("2完成");
    }

    @Async
    public Future<String> taskThere() throws Exception {
        log.info("任務3線程名稱 = {}", Thread.currentThread().getName());
        return new AsyncResult<>("執行任務3完成");
    }
}
           

調研方法和上面一樣,我們再來看下日志

任務2線程名稱 = my-xiaoxiao-AsyncExecutor-2
任務1線程名稱 = my-xiaoxiao-AsyncExecutor-1
任務3線程名稱 = SimpleAsyncTaskExecutor-1
總任務執行結束,總耗時=15 毫秒
           

通過日志我們可以看出 任務1和任務2走的是我們自定義的線程池,任務3還是走預設線程池。

五、CompletableFuture實作異步任務

推薦這種方式來實作異步,它不需要在啟動類上加@EnableAsync注解,也不需要在方法上加@Async注解,它實作更加優雅,而且CompletableFuture功能更加強大。

具體可以看下之前寫的文章:異步程式設計利器:CompletableFuture

1、CompletableFuture示例

看如何使用

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class CompletableTest {

    @Autowired
    private DemoTask dmoTask;
    
    @Test
    public void testCompletableThenRunAsync() throws Exception {
        long startTime = System.currentTimeMillis();

        CompletableFuture<Void> cp1 = CompletableFuture.runAsync(() -> {
            //任務1
            dmoTask.taskOne();
        });
        CompletableFuture<Void> cp2 = CompletableFuture.runAsync(() -> {
            //任務2
            dmoTask.taskTwo();
        });
        CompletableFuture<Void> cp3 = CompletableFuture.runAsync(() -> {
            //任務3
            dmoTask.taskThere();
        });
        
        cp1.get();
        cp2.get();
        cp3.get();
        //模拟主程式耗時時間
        System.out.println("總共用時" + (System.currentTimeMillis() - startTime) + "ms");
    }
}
           

檢視日志

===執行任務1===
===執行任務2===
===執行任務3===
任務3執行結束,總耗時=204 毫秒
任務2執行結束,總耗時=203 毫秒
任務1執行結束,總耗時=204 毫秒
總共用時226ms
           

從日志可以看出,通過CompletableFuture同樣可以實作異步執行任務!

原文連結:https://www.cnblogs.com/qdhxhz/p/16671089.html