爲什麼我的定時器不跑了

最近要搞個小服務運行在多家客戶的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阻塞被阻塞了,所有定時器都不跑了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章