springboot使用執行緒池
一、執行緒池執行流程圖
在Springboot中對使用執行緒池其進行了簡化處理,只需要配置一個型別為java.util.concurrent.TaskExecutor或其子類的bean,並在配置類或直接在程式入口類上宣告註解@EnableAsync
。
呼叫非同步方法也很簡單,在由Spring管理的物件的方法上標註註解@Async
,顯式呼叫即可生效。
一般使用Spring提供的ThreadPoolTaskExecutor類作為執行緒池物件。
二、springboot使用執行緒池demo
1、配置執行緒池
建立一個執行緒池的配置,讓Spring Boot載入,用來定義如何建立一個ThreadPoolTaskExecutor
@Configuration
和@EnableAsync
這兩個註解,表示這是個配置類,並且是執行緒池的配置類
@Configuration @EnableAsync public class ExecutorConfig { private static final Logger logger = LoggerFactory.getLogger(ExecutorConfig.class); @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; @Bean(name = "asyncServiceExecutor") public Executor asyncServiceExecutor() { logger.info("start asyncServiceExecutor"); ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //配置核心執行緒數 executor.setCorePoolSize(corePoolSize); //配置最大執行緒數 executor.setMaxPoolSize(maxPoolSize); //配置佇列大小 executor.setQueueCapacity(queueCapacity); //配置執行緒池中的執行緒的名稱字首 executor.setThreadNamePrefix(namePrefix); // rejection-policy:當pool已經達到max size的時候,如何處理新任務 // CALLER_RUNS:不在新執行緒中執行任務,而是有呼叫者所在的執行緒來執行 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); //執行初始化 executor.initialize(); return executor; } }
@Value
是我配置在application.properties
,可以參考配置,自由定義
# 非同步執行緒配置 # 配置核心執行緒數 async.executor.thread.core_pool_size = 5 # 配置最大執行緒數 async.executor.thread.max_pool_size = 5 # 配置佇列大小 async.executor.thread.queue_capacity = 99999 # 配置執行緒池中的執行緒的名稱字首 async.executor.thread.name.prefix = async-service-
2、建立非同步呼叫介面以及實現類
public interface AsyncService { /** * 執行非同步任務 * 可以根據需求,自己加引數擬定,我這裡就做個測試演示 */ void executeAsync(); }
實現類
@Service public class AsyncServiceImpl implements AsyncService { private static final Logger logger = LoggerFactory.getLogger(AsyncServiceImpl.class); @Override @Async("asyncServiceExecutor") public void executeAsync() { logger.info("start executeAsync"); System.out.println("非同步執行緒要做的事情"); System.out.println("可以在這裡執行批量插入等耗時的事情"); logger.info("end executeAsync"); } }
@Async("asyncServiceExecutor")表示指定使用asyncServiceExecutor執行緒池非同步執行該方法
注:@Async的呼叫涉及到動態代理,如果直接將需要非同步操作的方法寫到直接業務類中,業務類直接呼叫,則執行邏輯不會走到代理類,非同步就會失效!!!
ps:非同步失效原因①非同步方法使用註解@Async的返回值只能為void或者Future。
②沒有走Spring的代理類。因為像@Transactional和@Async註解的實現都是基於Spring的AOP,而AOP的實現是基於動態代理模式實現的。那麼註解失效的原因就很明顯了,有可能因為呼叫方法的是物件本身而不是代理物件,因為沒有經過Spring容器
3、呼叫非同步方法
接下來就是在Controller裡或者是哪裡通過註解@Autowired
注入這個Service
@Autowired private AsyncService asyncService; @GetMapping("/async") public void async(){ asyncService.executeAsync(); }
用postman或者其他工具來多次測試請求一下
2018-07-16 22:15:47.655 INFO 10516 --- [async-service-5] c.u.d.e.executor.impl.AsyncServiceImpl : start executeAsync 非同步執行緒要做的事情 可以在這裡執行批量插入等耗時的事情 2018-07-16 22:15:47.655 INFO 10516 --- [async-service-5] c.u.d.e.executor.impl.AsyncServiceImpl : end executeAsync 2018-07-16 22:15:47.770 INFO 10516 --- [async-service-1] c.u.d.e.executor.impl.AsyncServiceImpl : start executeAsync 非同步執行緒要做的事情 可以在這裡執行批量插入等耗時的事情 2018-07-16 22:15:47.770 INFO 10516 --- [async-service-1] c.u.d.e.executor.impl.AsyncServiceImpl : end executeAsync 2018-07-16 22:15:47.816 INFO 10516 --- [async-service-2] c.u.d.e.executor.impl.AsyncServiceImpl : start executeAsync
三、擴充套件知識(列印執行緒池執行狀況)
通過以上日誌可以發現,[async-service-]是有多個執行緒的,顯然已經在我們配置的執行緒池中執行了,並且每次請求中,controller的起始和結束日誌都是連續列印的,表明每次請求都快速響應了,而耗時的操作都留給執行緒池中的執行緒去非同步執行;
雖然我們已經用上了執行緒池,但是還不清楚執行緒池當時的情況,有多少執行緒在執行,多少在佇列中等待呢?這裡我建立了一個ThreadPoolTaskExecutor的子類,在每次提交執行緒的時候都會將當前執行緒池的執行狀況打印出來
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.util.concurrent.ListenableFuture; import java.util.concurrent.Callable; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; public class VisiableThreadPoolTaskExecutor extends ThreadPoolTaskExecutor { private static final Logger logger = LoggerFactory.getLogger(VisiableThreadPoolTaskExecutor.class); private void showThreadPoolInfo(String prefix) { ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor(); if (null == threadPoolExecutor) { return; } logger.info("{}, {},taskCount [{}], completedTaskCount [{}], activeCount [{}], queueSize [{}]", this.getThreadNamePrefix(), prefix, threadPoolExecutor.getTaskCount(), threadPoolExecutor.getCompletedTaskCount(), threadPoolExecutor.getActiveCount(), threadPoolExecutor.getQueue().size()); } @Override public void execute(Runnable task) { showThreadPoolInfo("1. do execute"); super.execute(task); } @Override public void execute(Runnable task, long startTimeout) { showThreadPoolInfo("2. do execute"); super.execute(task, startTimeout); } @Override public Future<?> submit(Runnable task) { showThreadPoolInfo("1. do submit"); return super.submit(task); } @Override public <T> Future<T> submit(Callable<T> task) { showThreadPoolInfo("2. do submit"); return super.submit(task); } @Override public ListenableFuture<?> submitListenable(Runnable task) { showThreadPoolInfo("1. do submitListenable"); return super.submitListenable(task); } @Override public <T> ListenableFuture<T> submitListenable(Callable<T> task) { showThreadPoolInfo("2. do submitListenable"); return super.submitListenable(task); } }
如上所示,showThreadPoolInfo方法中將任務總數、已完成數、活躍執行緒數,佇列大小都打印出來了,然後Override了父類的execute、submit等方法,在裡面呼叫showThreadPoolInfo方法,這樣每次有任務被提交到執行緒池的時候,都會將當前執行緒池的基本情況列印到日誌中;
修改ExecutorConfig.java的asyncServiceExecutor方法,將ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor()改為ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor()
@Bean(name = "asyncServiceExecutor") public Executor asyncServiceExecutor() { logger.info("start asyncServiceExecutor"); //在這裡修改 ThreadPoolTaskExecutor executor = new VisiableThreadPoolTaskExecutor(); //配置核心執行緒數 executor.setCorePoolSize(corePoolSize); //配置最大執行緒數 executor.setMaxPoolSize(maxPoolSize); //配置佇列大小 executor.setQueueCapacity(queueCapacity); //配置執行緒池中的執行緒的名稱字首 executor.setThreadNamePrefix(namePrefix); // rejection-policy:當pool已經達到max size的時候,如何處理新任務 // CALLER_RUNS:不在新執行緒中執行任務,而是有呼叫者所在的執行緒來執行 executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); //執行初始化 executor.initialize(); return executor; }
再次啟動該工程測試
2018-07-16 22:23:30.951 INFO 14088 --- [nio-8087-exec-2] u.d.e.e.i.VisiableThreadPoolTaskExecutor : async-service-, 2. do submit,taskCount [0], completedTaskCount [0], activeCount [0], queueSize [0] 2018-07-16 22:23:30.952 INFO 14088 --- [async-service-1] c.u.d.e.executor.impl.AsyncServiceImpl : start executeAsync 非同步執行緒要做的事情 可以在這裡執行批量插入等耗時的事情 2018-07-16 22:23:30.953 INFO 14088 --- [async-service-1] c.u.d.e.executor.impl.AsyncServiceImpl : end executeAsync 2018-07-16 22:23:31.351 INFO 14088 --- [nio-8087-exec-3] u.d.e.e.i.VisiableThreadPoolTaskExecutor : async-service-, 2. do submit,taskCount [1], completedTaskCount [1], activeCount [0], queueSize [0] 2018-07-16 22:23:31.353 INFO 14088 --- [async-service-2] c.u.d.e.executor.impl.AsyncServiceImpl : start executeAsync 非同步執行緒要做的事情 可以在這裡執行批量插入等耗時的事情 2018-07-16 22:23:31.353 INFO 14088 --- [async-service-2] c.u.d.e.executor.impl.AsyncServiceImpl : end executeAsync 2018-07-16 22:23:31.927 INFO 14088 --- [nio-8087-exec-5] u.d.e.e.i.VisiableThreadPoolTaskExecutor : async-service-, 2. do submit,taskCount [2], completedTaskCount [2], activeCount [0], queueSize [0] 2018-07-16 22:23:31.929 INFO 14088 --- [async-service-3] c.u.d.e.executor.impl.AsyncServiceImpl : start executeAsync
看紅色部分這說明提交任務到執行緒池的時候,呼叫的是submit(Callable task)這個方法,當前已經提交了2個任務,完成了2個,當前有0個執行緒在處理任務,還剩0個任務在佇列中等待,執行緒池的基本情況一路瞭然;
摘自:https://blog.csdn.net/m0_37701381/article/details/81072774