最近要搞個(gè)小服務(wù)運(yùn)行在多家客戶的windows服務(wù)器上,里面有兩個(gè)定時(shí)任務(wù),一個(gè)是定時(shí)檢查版本號(hào),一個(gè)是定時(shí)向服務(wù)器匯報(bào)狀態(tài),都使用 spring 的@Scheduled實(shí)現(xiàn)。
昨天晚上讓它們跑著,今天上午一看,居然沒有匯報(bào)狀態(tài)了,(無奈,肯定有bug)。
登錄服務(wù)器,看到服務(wù)還在運(yùn)行,看了一下沒有打日志了,訪問端口有數(shù)據(jù)返回,那這個(gè)服務(wù)應(yīng)該還活著,定時(shí)器不跑了?
沒有太多的辦法,老實(shí)下載了一個(gè)jdk安裝, jstack看一下,發(fā)現(xiàn)定時(shí)任務(wù)線程被阻塞了,而且居然是被RestTemplate阻塞的,想了一下,這個(gè)阻塞的原理可能比較復(fù)雜,暫不追究。
解決辦法挺簡單,老實(shí)地去設(shè)置了超時(shí)時(shí)間,想必下次不會(huì)阻塞這么久了。
等等,為什么我兩個(gè)定時(shí)器,一個(gè)線程阻塞了,還有一個(gè)去哪兒了? 這個(gè)不會(huì)是單線程吧?!
找到日志看一下,確實(shí)只有一個(gè)叫schedule-1的線程在跑,==!
有點(diǎn)超出我的想像,需要來看下源碼壓壓驚。
分析Spring的定時(shí)任務(wù)框架為什么線程是1, 從文檔入手,很好,在文檔中看到了配置類:

通過SchedulingConfiguration的初始化,創(chuàng)建了ScheduledAnnotationBeanPostProcessor來掃描代碼創(chuàng)建定時(shí)任務(wù)。
查看初始過程,發(fā)現(xiàn)創(chuàng)建TaskScheduler的創(chuàng)建過程:
// Search for TaskScheduler bean...
this.registrar.setTaskScheduler(resolveSchedulerBean(this.beanFactory, TaskScheduler.class, false));
beanFactory是DefaultListableBeanFactory, 查看resolveSchedulerBean中代碼:
private <T> T resolveSchedulerBean(BeanFactory beanFactory, Class<T> schedulerType, boolean byName) {
if (byName) {
//...
return scheduler;
}
else if (beanFactory instanceof AutowireCapableBeanFactory) {
NamedBeanHolder<T> holder = ((AutowireCapableBeanFactory) beanFactory).resolveNamedBean(schedulerType);
if (this.beanName != null && beanFactory instanceof ConfigurableBeanFactory) {
((ConfigurableBeanFactory) beanFactory).registerDependentBean(holder.getBeanName(), this.beanName);
}
return holder.getBeanInstance();
}
else {
return beanFactory.getBean(schedulerType);
}
}
這段是用beanFactory找到TaskScheduler.class的bean,并注冊依賴關(guān)系。
好吧,找下哪里配置了TaskScheduler,然后發(fā)現(xiàn)了TaskSchedulingAutoConfiguration:
@Bean
@ConditionalOnBean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)
@ConditionalOnMissingBean({ SchedulingConfigurer.class, TaskScheduler.class,
ScheduledExecutorService.class })
public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder builder) {
return builder.build();
}
@Bean
@ConditionalOnMissingBean
public TaskSchedulerBuilder taskSchedulerBuilder(TaskSchedulingProperties properties,
ObjectProvider<TaskSchedulerCustomizer> taskSchedulerCustomizers) {
TaskSchedulerBuilder builder = new TaskSchedulerBuilder();
builder = builder.poolSize(properties.getPool().getSize());
builder = builder.threadNamePrefix(properties.getThreadNamePrefix());
builder = builder.customizers(taskSchedulerCustomizers);
return builder;
}
可以看到這里設(shè)置了poolSize, 配置來自TaskSchedulingProperties, 激動(dòng)人心的時(shí)候到了,來看下TaskSchedulingProperties:
@ConfigurationProperties("spring.task.scheduling")
public class TaskSchedulingProperties {
//...
public static class Pool {
/**
* Maximum allowed number of threads.
*/
private int size = 1;
public int getSize() {
return this.size;
}
public void setSize(int size) {
this.size = size;
}
}
}
好吧,除了http阻塞的問題,一切都明白了。
spring boot 默認(rèn)開啟單線程的定時(shí)任務(wù)執(zhí)行器, 然后某個(gè)定時(shí)器因?yàn)閔ttp阻塞被阻塞了,所有定時(shí)器都不跑了。