最近要搞個小服務運行在多家客戶的windows服務器上,裏面有兩個定時任務,一個是定時檢查版本號,一個是定時向服務器彙報狀態,都使用 spring 的@Scheduled實現。
昨天晚上讓它們跑着,今天上午一看,居然沒有彙報狀態了,(無奈,肯定有bug)。
登錄服務器,看到服務還在運行,看了一下沒有打日誌了,訪問端口有數據返回,那這個服務應該還活着,定時器不跑了?
沒有太多的辦法,老實下載了一個jdk安裝, jstack看一下,發現定時任務線程被阻塞了,而且居然是被RestTemplate阻塞的,想了一下,這個阻塞的原理可能比較複雜,暫不追究。
解決辦法挺簡單,老實地去設置了超時時間,想必下次不會阻塞這麼久了。
等等,爲什麼我兩個定時器,一個線程阻塞了,還有一個去哪兒了? 這個不會是單線程吧?!
找到日誌看一下,確實只有一個叫schedule-1的線程在跑,==!
有點超出我的想像,需要來看下源碼壓壓驚。
分析Spring的定時任務框架爲什麼線程是1, 從文檔入手,很好,在文檔中看到了配置類:
通過SchedulingConfiguration的初始化,創建了ScheduledAnnotationBeanPostProcessor來掃描代碼創建定時任務。
查看初始過程,發現創建TaskScheduler的創建過程:
// 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,並註冊依賴關係。
好吧,找下哪裏配置了TaskScheduler,然後發現了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;
}
可以看到這裏設置了poolSize, 配置來自TaskSchedulingProperties, 激動人心的時候到了,來看下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 默認開啓單線程的定時任務執行器, 然後某個定時器因爲http阻塞被阻塞了,所有定時器都不跑了。