Spring使用ThreadPoolTaskExecutor自定義線程池及實現異步調用

多線程一直是工做或面試過程當中的高頻知識點,今天給你們分享一下使用 ThreadPoolTaskExecutor 來自定義線程池和實現異步調用多線程。java

1、ThreadPoolTaskExecutor

本文采用 Executors 的工廠方法進行配置。面試

一、將線程池用到的參數定義到配置文件中

在項目的 resources 目錄下建立 executor.properties 文件,並添加以下配置:spring

# 異步線程配置
# 核心線程數
async.executor.thread.core_pool_size=5
# 最大線程數
async.executor.thread.max_pool_size=8
# 任務隊列大小
async.executor.thread.queue_capacity=2
# 線程池中線程的名稱前綴
async.executor.thread.name.prefix=async-service-
# 緩衝隊列中線程的空閒時間
async.executor.thread.keep_alive_seconds=100

二、Executors 的工廠配置

2.一、配置詳情
@Configuration
// @PropertySource是找的target目錄下classes目錄下的文件,resources目錄下的文件編譯後會生成在classes目錄
@PropertySource(value = {"classpath:executor.properties"}, ignoreResourceNotFound=false, encoding="UTF-8")
@Slf4j
public class ExecutorConfig {

    @Value("${async.executor.thread.core_pool_size}")
    private int corePoolSize;
    @Value("${async.executor.thread.max_pool_size}")
    private int maxPoolSize;
    @Value("${async.executor.thread.queue_capacity}")
    private int queueCapacity;
    @Value("${async.executor.thread.name.prefix}")
    private String namePrefix;
    @Value("${async.executor.thread.keep_alive_seconds}")
    private int keepAliveSeconds;

    @Bean(name = "asyncTaskExecutor")
    public ThreadPoolTaskExecutor taskExecutor() {
        log.info("啓動");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        // 核心線程數
        executor.setCorePoolSize(corePoolSize);
        // 最大線程數
        executor.setMaxPoolSize(maxPoolSize);
        // 任務隊列大小
        executor.setQueueCapacity(queueCapacity);
        // 線程前綴名
        executor.setThreadNamePrefix(namePrefix);
        // 線程的空閒時間
        executor.setKeepAliveSeconds(keepAliveSeconds);
        // 拒絕策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 線程初始化
        executor.initialize();
        return executor;
    }
}
2.二、註解說明
  • **@Configuration:**Spring 容器在啓動時,會加載帶有 @Configuration 註解的類,對其中帶有 @Bean 註解的方法進行處理。
  • **@Bean:**是一個方法級別上的註解,主要用在 @Configuration 註解的類裏,也能夠用在 @Component 註解的類裏。添加的 bean 的 id 爲方法名。
  • **@PropertySource:**加載指定的配置文件。value 值爲要加載的配置文件,ignoreResourceNotFound 意思是若是加載的文件找不到,程序是否忽略它。默認爲 false 。若是爲 true ,則表明加載的配置文件不存在,程序不報錯。在實際項目開發中,最好設置爲 false 。若是 application.properties 文件中的屬性與自定義配置文件中的屬性重複,則自定義配置文件中的屬性值被覆蓋,加載的是 application.properties 文件中的配置屬性。
  • **@Slf4j:**lombok 的日誌輸出工具,加上此註解後,可直接調用 log 輸出各個級別的日誌。
  • **@Value:**調用配置文件中的屬性並給屬性賦予值。
2.三、線程池配置說明
  • **核心線程數:**線程池建立時候初始化的線程數。當線程數超過核心線程數,則超過的線程則進入任務隊列。微信

  • **最大線程數:**只有在任務隊列滿了以後纔會申請超過核心線程數的線程。不能小於核心線程數。多線程

  • **任務隊列:**線程數大於核心線程數的部分進入任務隊列。若是任務隊列足夠大,超出核心線程數的線程不會被建立,它會等待覈心線程執行完它們本身的任務後再執行任務隊列的任務,而不會再額外地建立線程。**舉例:**若是有20個任務要執行,核心線程數:10,最大線程數:20,任務隊列大小:2。則系統會建立18個線程。這18個線程有執行完任務的,再執行任務隊列中的任務。app

  • **線程的空閒時間:**當 線程池中的線程數量 大於 核心線程數 時,若是某線程空閒時間超過 keepAliveTime ,線程將被終止。這樣,線程池能夠動態的調整池中的線程數。異步

  • **拒絕策略:**若是(總任務數 - 核心線程數 - 任務隊列數)-(最大線程數 - 核心線程數)> 0 的話,則會出現線程拒絕。舉例:( 12 - 5 - 2 ) - ( 8 - 5 ) > 0,會出現線程拒絕。線程拒絕又分爲 4 種策略,分別爲:async

    • **CallerRunsPolicy():**交由調用方線程運行,好比 main 線程。
    • **AbortPolicy():**直接拋出異常。
    • **DiscardPolicy():**直接丟棄。
    • **DiscardOldestPolicy():**丟棄隊列中最老的任務。
2.四、線程池配置我的理解
  • 當一個任務被提交到線程池時,首先查看線程池的核心線程是否都在執行任務。若是沒有,則選擇一條線程執行任務。
  • 若是都在執行任務,查看任務隊列是否已滿。若是不滿,則將任務存儲在任務隊列中。核心線程執行完本身的任務後,會再處理任務隊列中的任務。
  • 若是任務隊列已滿,查看線程池(最大線程數控制)是否已滿。若是不滿,則建立一條線程去執行任務。若是滿了,就按照策略處理沒法執行的任務。

2、異步調用線程

一般 ThreadPoolTaskExecutor 是和 @Async 一塊兒使用。在一個方法上添加 @Async 註解,代表是異步調用方法函數。@Async 後面加上線程池的方法名或 bean 名稱,代表異步線程會加載線程池的配置。ide

@Component
@Slf4j
public class ThreadTest {
    /**
     * 每10秒循環一次,一個線程共循環10次。
     */
    @Async("asyncTaskExecutor")
    public void ceshi3() {
        for (int i = 0; i <= 10; i++) {
            log.info("ceshi3: " + i);
            try {
                Thread.sleep(2000 * 5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

備註:必定要在啓動類上添加 @EnableAsync 註解,這樣 @Async 註解纔會生效。函數

3、多線程使用場景

一、定時任務 @Scheduled

// 在啓動類上添加 @EnableScheduling 註解
@SpringBootApplication
@EnableScheduling
public class SpringBootStudyApplication {
   public static void main(String[] args) {
      SpringApplication.run(SpringBootStudyApplication.class, args);
   }
}
// @Component 註解將定時任務類歸入 spring bean 管理。
@Component
public class listennerTest3 {

    @Autowired
    private ThreadTest t;
	
    // 每1分鐘執行一次ceshi3()方法
    @Scheduled(cron = "0 0/1 * * * ?")
    public void run() {
        t.ceshi3();
    }
}

ceshi3() 方法調用線程池配置,且異步執行。

@Component
@Slf4j
public class ThreadTest {
    /**
     * 每10秒循環一次,一個線程共循環10次。
     */
    @Async("asyncTaskExecutor")
    public void ceshi3() {
        for (int i = 0; i <= 10; i++) {
            log.info("ceshi3: " + i);
            try {
                Thread.sleep(2000 * 5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

二、程序一啓動就異步執行多線程

經過繼承 CommandLineRunner 類實現。

@Component
public class ListennerTest implements CommandLineRunner {

    @Autowired
    private ThreadTest t;

    @Override
    public void run(String... args) {
        for (int i = 1; i <= 10; i++) {
            t.ceshi();
        }
    }
}
@Component
@Slf4j
public class ThreadTest {

    @Async("asyncTaskExecutor")
    public void ceshi() {
        log.info("ceshi");
    }
}

三、定義一個 http 接口

還能夠經過接口的形式來異步調用多線程:

@RestController
@RequestMapping("thread")
public class ListennerTest2 {

    @Autowired
    private ThreadTest t;

    @GetMapping("ceshi2")
    public void run() {
        for (int i = 1; i < 10; i++) {
            t.ceshi2();
        }
    }
}
@Component
@Slf4j
public class ThreadTest {

    @Async("asyncTaskExecutor")
    public void ceshi2() {
        for (int i = 0; i <= 3; i++) {
            log.info("ceshi2");
        }
    }
}

四、測試類

@RunWith(SpringRunner.class)
@SpringBootTest
public class ThreadRunTest {

    @Autowired
    private ThreadTest t;

    @Test
    public void thread1() {
        for (int i = 1; i <= 10; i++) {
            t.ceshi4();
        }
    }
}
@Component
@Slf4j
public class ThreadTest {
    @Async("asyncTaskExecutor")
    public void ceshi4() {
        log.info("ceshi4");
    }
}

4、總結

以上主要介紹了 ThreadPoolTaskExecutor 線程池的配置、使用、相關注解的意義及做用,也簡單介紹了使用 @Async 來異步調用線程,最後又列舉了多線程的使用場景,並配上了代碼示例。但願你們喜歡。


本文來自: 微信公衆號【大數據實戰演練】。閱讀更多精彩好文,歡迎關注微信公衆號【大數據實戰演練】。

相關文章
相關標籤/搜索