Spring Boot注解之@Async和自定義線程池


前言

  我們在學習線程池的時候,都知道線程池的核心線程數、最大線程數、線程工廠等核心參數非常重要,故熟記於心。但是有些工作五六年的攻城獅可能說不出來怎么初始化一個全局線程池,以在不同場景使用;所以,本文基於Spring Boot的異步注解@Async自定義全局線程池。如果是這方面的老鐵,請繞道而行,這里都是雕蟲小技。

maven坐標

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.7</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>31.0.1-jre</version>
        </dependency>

自定義線程池

  核心線程數和最大線程數等參數可以在配置文件中定義,這里為了簡單,就直接賦值了。

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.*;

/**
 * @Author: Wiener
 * @Date: 2022/1/8 19:57
 */
@Configuration
public class AsyncTaskConfig  {

    // 包括corePoolSize線程在內,多余的線程會等待keepAliveTime長時間,如果無請求可處理就自行銷毀
    @Bean("first-executor")
    public Executor firstExecutor() {
        ThreadFactory guavaFactory = new ThreadFactoryBuilder().setNameFormat("first-executor").build();
        int curSystemThreads = Runtime.getRuntime().availableProcessors() * 2;
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(curSystemThreads, 32,
                30L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(), guavaFactory);
        threadPool.allowsCoreThreadTimeOut();
        return threadPool;
    }
    //my-task用於指定線程池名字
    @Bean("my-executor")
    public Executor myExecutor() {
        ThreadFactory guavaFactory = new ThreadFactoryBuilder().setNameFormat("my-executor").build();
        int curSystemThreads = Runtime.getRuntime().availableProcessors() * 2;
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(curSystemThreads, 32,
                30L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(), guavaFactory);
        threadPool.allowsCoreThreadTimeOut();
        return threadPool;
    }

}

使用@Async注解創建線程任務

  Spring為任務調度與異步方法執行提供了注解@Async支持,通過在方法上標注@Async注解,可使得方法被異步調用。在需要異步執行的方法上加入@Async注解,並指定使用的線程池,當然可以不指定,直接寫@Async。

service定義如下:

/**
 *  指定pool
 * @Author: Wiener
 * @Date: 2022/1/8 19:59
 */
public interface AsyncTaskService {
    void executeAsyncTask(Integer i);
    void executeMyTask(Integer i);
}

實現類如下:

import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

/**
 * @Author: Wiener
 * @Date: 2022/1/9 12:33
 */
@Slf4j
@Service
public class AsyncTaskServiceImpl implements AsyncTaskService {

    @Async("first-executor")
    @Override
    public void executeAsyncTask(Integer i) {
        System.out.println("線程" + Thread.currentThread().getName() + " 執行異步任務:" + i);
        try {
            Thread.sleep(100);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("end executeAsync");
    }

    @Async("my-executor")
    @Override
    public void executeMyTask(Integer i) {
        System.out.println("---線程" + Thread.currentThread().getName() + " 執行異步任務:" + i);
        try {
            Thread.sleep(100);
        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println("---end executeAsync");
    }

}

修改啟動類和創建測試類

  在啟動類添加注解@EnableAsync,以支持異步操作。然后,創建一個controller:

import com.swagger.demo.service.AsyncTaskService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @author 測試線程池
 */
@Slf4j
@RestController
@RequestMapping("/pool")
public class GuavaController {
    @Autowired
    private AsyncTaskService asyncTaskService;

    @PostMapping("asyncTaskTest")
    public void asyncTaskTest() {
		// 請求參數寫為固定值
        asyncTaskService.executeAsyncTask(1);
        asyncTaskService.executeMyTask(200);
        System.out.println("All tasks finished.");
    }

}

  啟動Spring Boot服務后,請求如上API,在控制台可以看到如下日志:

線程first-executor 執行異步任務:1
All tasks finished.
---線程my-executor 執行異步任務:200
end executeAsync
---end executeAsync

從執行結果來看,我們配置的兩個執行器都成功了。

小結|@Async的使用方法

  在使用@Async注解的時候,有以下幾點需要注意,

  1. 返回值:若不需要返回值,則直接設置方法返回值為void;若需要,則返回值用AsyncResult或者CompletableFuture承接。
  2. 自定義執行器,例如:@Async("theGivenExecutor")。
  3. 如何開啟異步:@Async注解必須在不同類間調用,如 A類—>B類.methodC()(@Async添加在B類的方法methodC上)。若在同一個類中進行方法嵌套調用,會變同步執行,例如:A類.methodB()—>A類.@Async methodC()。
  4. @Async加到類上,表示這個類的所有方法都是異步執行,並且方法上的注解會覆蓋類上的注解。慎用此種使用注解的方式!

  老鐵們, 因個人能力有限,難免有瑕疵,如果發現bug或者有更好的建議,那么請在文章下方留言!

Reference

https://www.jianshu.com/p/832f2b162450


免責聲明!

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



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