Table of Contents
ToggleSpring Boot ThreadPool
為了降低資源損耗,重用執行緒、減少執行緒建立和切換所帶來的開銷,提供了執行緒連線池管理、設定連線、閒置存活時間等功能, Spring Boot Thread Pool Configuration 增加了範例,並透過 JUnit 5 單元測試來驗證產出結果。
功能簡介
ThreadPool 是一個執行緒池,其原理類似資料庫連接池,池中通常有固定或變動數量的執行緒,每當有任務要執行時,就使用池中的執行緒來處理,執行完成後,該執行緒放回池中,繼續等待下個任務。
檔案目錄
./
+- build.gradle
+- src
+- main
+- java
+- org
+- ruoxue
+- spring_boot_168
+- config
+- ThreadPoolConfig.java
sysThreadPool
Spring Boot Threading 系統連線池,設定連線池大小、閒置存活時間等。
@Bean(value = "sysThreadPool")
public ExecutorService sysThreadPool() {
String prefix = "SYS-T-";
ExecutorService executor = new ThreadPoolExecutor(CORE_POOL_SIZE, //
MAX_POOLSIZE, //
KEEP_LIVE_TIME, //
TimeUnit.MILLISECONDS, //
new SynchronousQueue<>(), //
new NamedThreadFactory(prefix), //
new ThreadPoolExecutor.CallerRunsPolicy() //
);
log.info("prefix: " + prefix);
return executor;
}
gameThreadPool
Spring Boot Threading 遊戲連線池,設定連線池大小、閒置存活時間等。
@Bean(value = "gameThreadPool")
public ExecutorService gameThreadPool() {
String prefix = "GAME-T-";
ExecutorService executor = new ThreadPoolExecutor(CORE_POOL_SIZE, //
MAX_POOLSIZE, //
KEEP_LIVE_TIME, //
TimeUnit.MILLISECONDS, //
new SynchronousQueue<>(), //
new NamedThreadFactory(prefix), //
new ThreadPoolExecutor.CallerRunsPolicy() //
);
log.info("prefix: " + prefix);
return executor;
}
mqThreadPool
Spring Boot Threading 佇列訊息連線池,設定連線池大小、閒置存活時間等。
@Bean(value = "mqThreadPool")
public ExecutorService mqThreadPool() {
String prefix = "MQ-T-";
ExecutorService executor = new ThreadPoolExecutor(CORE_POOL_SIZE, //
MAX_POOLSIZE, //
KEEP_LIVE_TIME, //
TimeUnit.MILLISECONDS, //
new SynchronousQueue<>(), //
new NamedThreadFactory(prefix), //
new ThreadPoolExecutor.CallerRunsPolicy() //
);
log.info("prefix: " + prefix);
return executor;
}
ThreadPoolConfig.java
新增檔案,依照用途,建立多個連線池,設定連線池大小、閒置存活時間等。
ThreadPool Java
- sysThreadPool 前綴 SYS-T- 開頭,用於處理系統任務,如:定時統計報表,同步資料等用途。
- gameThreadPool 前綴 GAME-T- 開頭,用於處理遊戲任務,如:統計線上人數等用途。
- mqThreadPool 前綴 MQ-T- 開頭,用於處理 MQ 任務,如:發送訊息,交換資料等用途
package org.ruoxue.spring_boot_168.config;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.TimeUnit;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import lombok.extern.slf4j.Slf4j;
@Configuration
@Slf4j
public class ThreadPoolConfig {
public static final int CORE_POOL_SIZE = Runtime.getRuntime().availableProcessors();
public static final int MAX_POOLSIZE = 50 * CORE_POOL_SIZE;
public static final long KEEP_LIVE_TIME = 60000L;
public static final int QUEUE_CAPACITY = 10;
@Bean(value = "sysThreadPool")
public ExecutorService sysThreadPool() {
String prefix = "SYS-T-";
ExecutorService executor = new ThreadPoolExecutor(CORE_POOL_SIZE, //
MAX_POOLSIZE, //
KEEP_LIVE_TIME, //
TimeUnit.MILLISECONDS, //
new SynchronousQueue<>(), //
new NamedThreadFactory(prefix), //
new CallerRunsPolicy() //
);
log.info("prefix: " + prefix);
return executor;
}
@Bean(value = "gameThreadPool")
public ExecutorService gameThreadPool() {
String prefix = "GAME-T-";
ExecutorService executor = new ThreadPoolExecutor(CORE_POOL_SIZE, //
MAX_POOLSIZE, //
KEEP_LIVE_TIME, //
TimeUnit.MILLISECONDS, //
new SynchronousQueue<>(), //
new NamedThreadFactory(prefix), //
new CallerRunsPolicy() //
);
log.info("prefix: " + prefix);
return executor;
}
@Bean(value = "mqThreadPool")
public ExecutorService mqThreadPool() {
String prefix = "MQ-T-";
ExecutorService executor = new ThreadPoolExecutor(CORE_POOL_SIZE, //
MAX_POOLSIZE, //
KEEP_LIVE_TIME, //
TimeUnit.MILLISECONDS, //
new SynchronousQueue<>(), //
new NamedThreadFactory(prefix), //
new CallerRunsPolicy() //
);
log.info("prefix: " + prefix);
return executor;
}
public static class NamedThreadFactory implements ThreadFactory {
private final AtomicInteger count = new AtomicInteger();
private final String prefix;
public NamedThreadFactory(String prefix) {
this.prefix = prefix;
}
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
int c = count.incrementAndGet();
thread.setName(prefix + "-" + c);
return thread;
}
}
}
單元測試
ThreadPoolConfigTest.java
Multithreading in Java Spring Boot 新增檔案,驗證是否符合預期 。
package org.ruoxue.spring_boot_168.config;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.ruoxue.spring_boot_168.Application;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = Application.class)
public class ThreadPoolConfigTest {
@Autowired
private ThreadPoolConfig config;
@Test
public void config() {
assertNotNull(config);
}
}
config
測試方法上點右鍵執行 Run As -> JUnit Test ,查看 console 。
2022-06-17T00:39:10.763+0800 [main] INFO sysThreadPool:32 - prefix: SYS-T-
2022-06-17T00:39:10.763+0800 [main] INFO sysThreadPool:33 - corePoolSize: 6
2022-06-17T00:39:10.764+0800 [main] INFO sysThreadPool:34 - maximumPoolSize: 300
2022-06-17T00:39:10.767+0800 [main] INFO gameThreadPool:49 - prefix: GAME-T-
2022-06-17T00:39:10.768+0800 [main] INFO gameThreadPool:50 - corePoolSize: 6
2022-06-17T00:39:10.768+0800 [main] INFO gameThreadPool:51 - maximumPoolSize: 300
2022-06-17T00:39:10.768+0800 [main] INFO mqThreadPool:66 - prefix: MQ-T-
2022-06-17T00:39:10.769+0800 [main] INFO mqThreadPool:67 - corePoolSize: 6
2022-06-17T00:39:10.769+0800 [main] INFO mqThreadPool:68 - maximumPoolSize: 300
心得分享
Spring Multithread 系統中有許多不同類型的任務,可考慮建立多個 ThreadPool ,來因應不同的任務需求,避免不同類型任務互搶同一池的執行緒 ,當再也無法分配空閒的執行緒時,當下任務可能會發生堵塞等待,或執行失敗等狀況,事前的規劃與分配,將可以避免許多不可預期的狀況。