【小家Spring】Spring任务调度@Scheduled的使用以及原理、源码分析(@EnableScheduling)

每篇一句

你若混好了,放个屁都是道理。你若混差了,讲个道理都是放屁

相关阅读

【小家Spring】Spring任务调度核心接口(类)之—TaskScheduler(任务调度器)、Trigger(触发器)、ScheduledTask(调度任务)详解
【小家java】Java定时任务ScheduledThreadPoolExecutor详解以及与Timer、TimerTask的区别(执行指定次数停止任务)
【小家Spring】Spring异步处理@Async的使用以及原理、源码分析(@EnableAsync)

【小家java】用 ThreadPoolExecutor/ThreadPoolTaskExecutor 线程池技术提高系统吞吐量(附带线程池参数详解和使用注意事项)
【小家Java】一次Java线程池误用(newFixedThreadPool)引发的线上血案和总结

前言

JDK给我们提供了定时任务的能力,详解之前有篇博文:
【小家java】Java定时任务ScheduledThreadPoolExecutor详解以及与Timer、TimerTask的区别(执行指定次数停止任务)

而Spring基于此做了更便捷的封装,使得我们使用起来异常的方便~

定时任务也是平时开发不可缺少的一个使用场景,本文主要看看Spring是怎么来实现这一套逻辑的?

Demo
@Service
public class HelloServiceImpl implements HelloService {// @Schedules// 它是允许重复注解的~~~~//@Scheduled(cron = "0/5 * * * * ?")@Scheduled(cron = "0/2 * * * * ?") // 每2秒钟执行一次public void job1() {System.out.println("我执行了~~" + LocalTime.now());}
}

然后让Spring开启定时任务的支持即可。

@Configuration
@EnableScheduling // 开启定时任务
public class RootConfig {}

输出如下:每两秒钟执行一次

我执行了~~14:46:36.003
我执行了~~14:46:38.002
我执行了~~14:46:40.001
我执行了~~14:46:42.001
我执行了~~14:46:44.002
...

原理、源码分析

很显然又是@EnableXXX的设计模式,因此入口又从该注解开始吧~~~

@EnableScheduling
//@since 3.1
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(SchedulingConfiguration.class)
@Documented
public @interface EnableScheduling {}

可以看出它没有任何属性。所以只有一个@Import在起作用,因此重点看看SchedulingConfiguration

它的效果同XML中的

SchedulingConfiguration
@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class SchedulingConfiguration {@Bean(name = TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME)@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public ScheduledAnnotationBeanPostProcessor scheduledAnnotationProcessor() {return new ScheduledAnnotationBeanPostProcessor();}}

同样的,也非常非常的简单。只是向容器注入了一个ScheduledAnnotationBeanPostProcessor,它是一个PostProcessor,同时也是一个SmartInitializingSingleton等等

ScheduledAnnotationBeanPostProcessor

Scheduled注解后处理器,项目启动时会扫描所有标记了@Scheduled注解的方法,封装成ScheduledTask注册起来。

这个处理器是处理定时任务的核心类,比较复杂。下面也是结合源码,来看看它具体的一个工作内容:

// 首先:非常震撼的是,它实现的接口非常的多。还好的是,大部分接口我们都很熟悉了。
// MergedBeanDefinitionPostProcessor:它是个BeanPostProcessor
// DestructionAwareBeanPostProcessor:在销毁此Bean的时候,会调用对应方法
// SmartInitializingSingleton:它会在所有的单例Bean都完成了初始化后,调用这个接口的方法
// EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware:都是些感知接口
// DisposableBean:该Bean销毁的时候会调用
// ApplicationListener:监听容器的`ContextRefreshedEvent`事件
// ScheduledTaskHolder:维护本地的ScheduledTask实例
public class ScheduledAnnotationBeanPostProcessorimplements ScheduledTaskHolder, MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor,Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware,SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {/*** The default name of the {@link TaskScheduler} bean to pick up: "taskScheduler".* 

Note that the initial lookup happens by type; this is just the fallback* in case of multiple scheduler beans found in the context.* @since 4.2*/// 看着注释就知道,和@Async的默认处理一样~~~~先类型 在回退到名称public static final String DEFAULT_TASK_SCHEDULER_BEAN_NAME = "taskScheduler";// 调度器(若我们没有配置,它是null的)@Nullableprivate Object scheduler;// 这些都是Awire感知接口注入进来的~~@Nullableprivate StringValueResolver embeddedValueResolver;@Nullableprivate String beanName;@Nullableprivate BeanFactory beanFactory;@Nullableprivate ApplicationContext applicationContext;// ScheduledTaskRegistrar:ScheduledTask注册中心,ScheduledTaskHolder接口的一个重要的实现类,维护了程序中所有配置的ScheduledTask// 内部会处理调取器得工作,因此我建议先移步,看看这个类得具体分析private final ScheduledTaskRegistrar registrar = new ScheduledTaskRegistrar();// 缓存,没有被标注注解的class们 // 这有个技巧,使用了newSetFromMap,自然而然的这个set也就成了一个线程安全的setprivate final Set<Class<?>> nonAnnotatedClasses = Collections.newSetFromMap(new ConcurrentHashMap<>(64));// 缓存对应的Bean上 里面对应的 ScheduledTask任务。可议有多个哦~~// 注意:此处使用了IdentityHashMapprivate final Map<Object, Set<ScheduledTask>> scheduledTasks = new IdentityHashMap<>(16);// 希望此processor是最后执行的~@Overridepublic int getOrder() {return LOWEST_PRECEDENCE;}//Set the {@link org.springframework.scheduling.TaskScheduler} that will invoke the scheduled methods// 也可以是JDK的ScheduledExecutorService(内部会给你包装成一个TaskScheduler)// 若没有指定。那就会走默认策略:去从起中先按照类型找`TaskScheduler`该类型(或者ScheduledExecutorService这个类型也成)的。// 若有多个该类型或者找不到,就安好"taskScheduler"名称去找 // 再找不到,就用系统默认的:public void setScheduler(Object scheduler) {this.scheduler = scheduler;}...// 此方法会在该容器内所有的单例Bean已经初始化全部结束后,执行@Overridepublic void afterSingletonsInstantiated() {// Remove resolved singleton classes from cache// 因为已经是最后一步了,所以这个缓存可议清空了this.nonAnnotatedClasses.clear();// 在容器内运行,ApplicationContext都不会为nullif (this.applicationContext == null) {// Not running in an ApplicationContext -> register tasks early...// 如果不是在ApplicationContext下运行的,那么就应该提前注册这些任务finishRegistration();}}// 兼容容器刷新的时间(此时候容器硬启动完成了) 它还在`afterSingletonsInstantiated`的后面执行@Overridepublic void onApplicationEvent(ContextRefreshedEvent event) {// 这个动作务必要做:因为Spring可能有多个容器,所以可能会发出多个ContextRefreshedEvent 事件// 显然我们只处理自己容器发出来得事件,别的容器发出来我不管~~if (event.getApplicationContext() == this.applicationContext) {// Running in an ApplicationContext -> register tasks this late...// giving other ContextRefreshedEvent listeners a chance to perform// their work at the same time (e.g. Spring Batch's job registration).// 为其他ContextRefreshedEvent侦听器提供同时执行其工作的机会(例如,Spring批量工作注册)finishRegistration();}}private void finishRegistration() {// 如果setScheduler了,就以调用者指定的为准~~~if (this.scheduler != null) {this.registrar.setScheduler(this.scheduler);}// 这里继续厉害了:从容器中找到所有的接口`SchedulingConfigurer`的实现类(我们可议通过实现它定制化scheduler)if (this.beanFactory instanceof ListableBeanFactory) {Map<String, SchedulingConfigurer> beans =((ListableBeanFactory) this.beanFactory).getBeansOfType(SchedulingConfigurer.class);List<SchedulingConfigurer> configurers = new ArrayList<>(beans.values());// 同@Async只允许设置一个不一样的是,这里每个都会让它生效// 但是平时使用,我们自顶一个类足矣~~~AnnotationAwareOrderComparator.sort(configurers);for (SchedulingConfigurer configurer : configurers) {configurer.configureTasks(this.registrar);}}// 至于task是怎么注册进registor的,请带回看`postProcessAfterInitialization`这个方法的实现// 有任务并且registrar.getScheduler() == null,那就去容器里找来试试~~~if (this.registrar.hasTasks() && this.registrar.getScheduler() == null) {...// 这块逻辑和@Async的处理一毛一样。忽略了 主要看看resolveSchedulerBean()这个方法即可}this.registrar.afterPropertiesSet();}// 从容器中去找一个private <T> T resolveSchedulerBean(BeanFactory beanFactory, Class<T> schedulerType, boolean byName) {// 若按名字去查找,那就按照名字找if (byName) {T scheduler = beanFactory.getBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, schedulerType);// 这个处理非常非常有意思,就是说倘若找到了你可以在任意地方直接@Autowired这个Bean了,可以拿这个共用Scheduler来调度我们自己的任务啦~~if (this.beanName != null && this.beanFactory instanceof ConfigurableBeanFactory) {((ConfigurableBeanFactory) this.beanFactory).registerDependentBean(DEFAULT_TASK_SCHEDULER_BEAN_NAME, this.beanName);}return scheduler;}// 按照schedulerType该类型的名字匹配resolveNamedBean 底层依赖:getBeanNamesForTypeelse 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);}}@Overridepublic void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {}@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) {return bean;}// Bean初始化完成后执行。去看看Bean里面有没有标注了@Scheduled的方法~~@Overridepublic Object postProcessAfterInitialization(final Object bean, String beanName) {// 拿到目标类型(因为此类有可能已经被代理过)Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);// 这里对没有标注注解的类做了一个缓存,防止从父去扫描(毕竟可能有多个容器,可能有重复扫描的现象)if (!this.nonAnnotatedClasses.contains(targetClass)) {// 如下:主要用到了MethodIntrospector.selectMethods 这个内省方法工具类的这个g工具方法,去找指定Class里面,符合条件的方法们Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,(MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {//过滤Method的核心逻辑就是是否标注有此注解(Merged表示标注在父类、或者接口处也是ok的)Set<Scheduled> scheduledMethods = AnnotatedElementUtils.getMergedRepeatableAnnotations(method, Scheduled.class, Schedules.class);return (!scheduledMethods.isEmpty() ? scheduledMethods : null);});if (annotatedMethods.isEmpty()) {this.nonAnnotatedClasses.add(targetClass);if (logger.isTraceEnabled()) {logger.trace("No @Scheduled annotations found on bean class: " + bean.getClass());}}// 此处相当于已经找到了对应的注解方法~~~else {// Non-empty set of methods// 这里有一个双重遍历。因为一个方法上,可能重复标注多个这样的注解~~~~~// 所以最终遍历出来后,就交给processScheduled(scheduled, method, bean)去处理了annotatedMethods.forEach((method, scheduledMethods) ->scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));if (logger.isDebugEnabled()) {logger.debug(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName + "': " + annotatedMethods);}}}return bean;}// 这个方法就是灵魂了。就是执行这个注解,最终会把这个任务注册进去,并且启动的~~~protected void processScheduled(Scheduled scheduled, Method method, Object bean) {try {// 标注此注解的方法必须是无参的方法Assert.isTrue(method.getParameterCount() == 0, "Only no-arg methods may be annotated with @Scheduled");// 拿到最终要被调用的方法 做这么一步操作主要是防止方法被代理了Method invocableMethod = AopUtils.selectInvocableMethod(method, bean.getClass());// 把该方法包装成一个Runnable 线程~~~Runnable runnable = new ScheduledMethodRunnable(bean, invocableMethod);boolean processedSchedule = false;String errorMessage = "Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";// 装载任务,这里长度定为4,因为Spring认为标注4个注解还不够你用的?Set<ScheduledTask> tasks = new LinkedHashSet<>(4);// Determine initial delay// 计算出延时多长时间执行 initialDelayString 支持占位符如:@Scheduled(fixedDelayString = "${time.fixedDelay}")// 这段话得意思是,最终拿到一个initialDelay值~~~~~Long型的long initialDelay = scheduled.initialDelay();String initialDelayString = scheduled.initialDelayString();if (StringUtils.hasText(initialDelayString)) {Assert.isTrue(initialDelay < 0, "Specify 'initialDelay' or 'initialDelayString', not both");if (this.embeddedValueResolver != null) {initialDelayString = this.embeddedValueResolver.resolveStringValue(initialDelayString);}if (StringUtils.hasLength(initialDelayString)) {try {initialDelay = parseDelayAsLong(initialDelayString);}catch (RuntimeException ex) {throw new IllegalArgumentException("Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into long");}}}// Check cron expression// 解析cronString cron = scheduled.cron();if (StringUtils.hasText(cron)) {String zone = scheduled.zone();// 由此课件,cron也可以使用占位符。把它配置在配置文件里就成~~~zone也是支持占位符的if (this.embeddedValueResolver != null) {cron = this.embeddedValueResolver.resolveStringValue(cron);zone = this.embeddedValueResolver.resolveStringValue(zone);}if (StringUtils.hasLength(cron)) {Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");processedSchedule = true;TimeZone timeZone;if (StringUtils.hasText(zone)) {timeZone = StringUtils.parseTimeZoneString(zone);}else {timeZone = TimeZone.getDefault();}// 这个相当于,如果配置了cron,它就是一个task了,就可以吧任务注册进registrar里面了// 这里面的处理是。如果已经有调度器taskScheduler了,那就立马准备执行了 tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));}}// At this point we don't need to differentiate between initial delay set or not anymoreif (initialDelay < 0) {initialDelay = 0;}...// 下面就不再说了,就是解析fixed delay、fixed rated、// Check whether we had any attribute setAssert.isTrue(processedSchedule, errorMessage);// Finally register the scheduled tasks// 最后吧这些任务都放在全局属性里保存起来~~~~// getScheduledTasks()方法是会把所有的任务都返回出去的~~~ScheduledTaskHolder接口就一个Set getScheduledTasks();方法嘛synchronized (this.scheduledTasks) {Set<ScheduledTask> registeredTasks = this.scheduledTasks.get(bean);if (registeredTasks == null) {registeredTasks = new LinkedHashSet<>(4);this.scheduledTasks.put(bean, registeredTasks);}registeredTasks.addAll(tasks);}}catch (IllegalArgumentException ex) {throw new IllegalStateException("Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());}}private static long parseDelayAsLong(String value) throws RuntimeException {if (value.length() > 1 && (isP(value.charAt(0)) || isP(value.charAt(1)))) {return Duration.parse(value).toMillis();}return Long.parseLong(value);}private static boolean isP(char ch) {return (ch == 'P' || ch == 'p');}//@since 5.0.2 获取到所有的任务。包含本实例的,以及registrar(手动注册)的所有任务@Overridepublic Set<ScheduledTask> getScheduledTasks() {Set<ScheduledTask> result = new LinkedHashSet<>();synchronized (this.scheduledTasks) {Collection<Set<ScheduledTask>> allTasks = this.scheduledTasks.values();for (Set<ScheduledTask> tasks : allTasks) {result.addAll(tasks);}}result.addAll(this.registrar.getScheduledTasks());return result;}// Bean销毁之前执行。移除掉所有的任务,并且取消所有的任务@Overridepublic void postProcessBeforeDestruction(Object bean, String beanName) {Set<ScheduledTask> tasks;synchronized (this.scheduledTasks) {tasks = this.scheduledTasks.remove(bean);}if (tasks != null) {for (ScheduledTask task : tasks) {task.cancel();}}}... }

这里可以说已经把Spring怎么发现Task、执行Task的流程讲解通了。这里忽略了一个重要的类:ScheduledTaskRegistrar,它整体作为一个注册中心的角色,非常的重要,下面讲解它:

ScheduledTaskRegistrar

ScheduledTask注册中心,ScheduledTaskHolder接口的一个重要的实现类,维护了程序中所有配置的ScheduledTask

它是ScheduledAnnotationBeanPostProcessor的一个重要角色。

//@since 3.0 它在Spring3.0就有了
// 这里面又有一个重要的接口:我们可议通过扩展实现此接口,来定制化属于自己的ScheduledTaskRegistrar 下文会有详细介绍
// 它实现了InitializingBean和DisposableBean,所以我们也可以把它放进容器里面
public class ScheduledTaskRegistrar implements ScheduledTaskHolder, InitializingBean, DisposableBean {// 任务调度器@Nullableprivate TaskScheduler taskScheduler;// 该类事JUC包中的类@Nullableprivate ScheduledExecutorService localExecutor;// 对任务进行分类 管理@Nullableprivate List<TriggerTask> triggerTasks;@Nullableprivate List<CronTask> cronTasks;@Nullableprivate List<IntervalTask> fixedRateTasks;@Nullableprivate List<IntervalTask> fixedDelayTasks;private final Map<Task, ScheduledTask> unresolvedTasks = new HashMap<>(16);private final Set<ScheduledTask> scheduledTasks = new LinkedHashSet<>(16);// 调用者可议自己指定一个TaskScheduler public void setTaskScheduler(TaskScheduler taskScheduler) {Assert.notNull(taskScheduler, "TaskScheduler must not be null");this.taskScheduler = taskScheduler;}// 这里,如果你指定的是个TaskScheduler、ScheduledExecutorService都是阔仪得// ConcurrentTaskScheduler也是一个TaskScheduler的实现类public void setScheduler(@Nullable Object scheduler) {if (scheduler == null) {this.taskScheduler = null;}else if (scheduler instanceof TaskScheduler) {this.taskScheduler = (TaskScheduler) scheduler;}else if (scheduler instanceof ScheduledExecutorService) {this.taskScheduler = new ConcurrentTaskScheduler(((ScheduledExecutorService) scheduler));}else {throw new IllegalArgumentException("Unsupported scheduler type: " + scheduler.getClass());}}@Nullablepublic TaskScheduler getScheduler() {return this.taskScheduler;}// 将触发的任务指定为可运行文件(任务)和触发器对象的映射  typically custom implementations of the {@link Trigger} interface// org.springframework.scheduling.Triggerpublic void setTriggerTasks(Map<Runnable, Trigger> triggerTasks) {this.triggerTasks = new ArrayList<>();triggerTasks.forEach((task, trigger) -> addTriggerTask(new TriggerTask(task, trigger)));}// 主要处理` `这种配置public void setTriggerTasksList(List<TriggerTask> triggerTasks) {this.triggerTasks = triggerTasks;}public List<TriggerTask> getTriggerTaskList() {return (this.triggerTasks != null? Collections.unmodifiableList(this.triggerTasks) :Collections.emptyList());}// 这个一般是最常用的 CronTrigger:Trigger的一个实现类。另一个实现类为PeriodicTriggerpublic void setCronTasks(Map<Runnable, String> cronTasks) {this.cronTasks = new ArrayList<>();cronTasks.forEach(this::addCronTask);}public void setCronTasksList(List<CronTask> cronTasks) {this.cronTasks = cronTasks;}public List<CronTask> getCronTaskList() {return (this.cronTasks != null ? Collections.unmodifiableList(this.cronTasks) :Collections.emptyList());}...// 判断是否还有任务public boolean hasTasks() {return (!CollectionUtils.isEmpty(this.triggerTasks) ||!CollectionUtils.isEmpty(this.cronTasks) ||!CollectionUtils.isEmpty(this.fixedRateTasks) ||!CollectionUtils.isEmpty(this.fixedDelayTasks));}/*** Calls {@link #scheduleTasks()} at bean construction time.*/// 这个方法很重要:开始执行所有已经注册的任务们~~@Overridepublic void afterPropertiesSet() {scheduleTasks();}@SuppressWarnings("deprecation")protected void scheduleTasks() {// 这一步非常重要:如果我们没有指定taskScheduler ,这里面会new一个newSingleThreadScheduledExecutor// 显然它并不是是一个真的线程池,所以他所有的任务还是得一个一个的One by One的执行的  请务必注意啊~~~~// 默认是它:Executors.newSingleThreadScheduledExecutor() 所以肯定串行啊if (this.taskScheduler == null) {this.localExecutor = Executors.newSingleThreadScheduledExecutor();this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);}// 加下来做得事,就是借助TaskScheduler来启动每个任务// 并且把启动了的任务最终保存到scheduledTasks里面~~~ 后面还会介绍TaskScheduler的两个实现if (this.triggerTasks != null) {for (TriggerTask task : this.triggerTasks) {addScheduledTask(scheduleTriggerTask(task));}}if (this.cronTasks != null) {for (CronTask task : this.cronTasks) {addScheduledTask(scheduleCronTask(task));}}if (this.fixedRateTasks != null) {for (IntervalTask task : this.fixedRateTasks) {addScheduledTask(scheduleFixedRateTask(task));}}if (this.fixedDelayTasks != null) {for (IntervalTask task : this.fixedDelayTasks) {addScheduledTask(scheduleFixedDelayTask(task));}}}private void addScheduledTask(@Nullable ScheduledTask task) {if (task != null) {this.scheduledTasks.add(task);}}@Overridepublic Set<ScheduledTask> getScheduledTasks() {return Collections.unmodifiableSet(this.scheduledTasks);}// 销毁: task.cancel()取消所有的任务  调用的是Future.cancel()// 并且关闭线程池localExecutor.shutdownNow()@Overridepublic void destroy() {for (ScheduledTask task : this.scheduledTasks) {task.cancel();}if (this.localExecutor != null) {this.localExecutor.shutdownNow();}}}
展示默认情况下任务不能并行的Demo

首先,采用全默认配置执行定时器:

    @Scheduled(cron = "0/2 * * * * ?")public void fun1(){System.out.println(Thread.currentThread().getName());// 模拟任务内抛出异常~~~~throw new RuntimeException("aaaaaaaa");}pool-2-thread-1_61
pool-2-thread-1_61
pool-2-thread-1_61
pool-2-thread-1_61
...

从这里可以得出结论:默认情况下使用的使用的是线程池但是因为只有一个线程,所以使用的都是它。但是需要注意的是,虽然是同一个线程但是即使内部抛出了异常,线程也不会终止的,所以请放心。原因如下:
DelegatingErrorHandlingRunnable#run

public class DelegatingErrorHandlingRunnable implements Runnable {...@Overridepublic void run() {try {this.delegate.run();}catch (UndeclaredThrowableException ex) {this.errorHandler.handleError(ex.getUndeclaredThrowable());}catch (Throwable ex) {this.errorHandler.handleError(ex);}}...	
}

人家都给你try住了,并且还交给了你自定义的errorHandler去处理,可谓还是非常友好的~

让任务并行执行?

不解释了,就是自定义配置一个线程池给它,提高执行效率。具体请参照下面有标准使用方式~~~

备注:SpringBoot本身默认的执行方式是串行执行,无论有多少task,都是一个线程串行执行。若你所想提高效率,请提供线程池

@Scheduled注解各属性含义

参考:@Scheduled注解各参数详解

Quartz和Spring schedule简单对比

Quartz是个著名的、强大的、开源的任务调度框架。下面来几点非常简单的对比,供各位参考:

备注:这里只谈单机版的,不说分布式的,在分布式中有其他的框架可以解决,同时quartz也是可以支持分布式的。

  • schedule:支持cron、不支持持久化、开发非常非常非常简单
  • quartz:支持cron、支持持久化、开发复杂

虽然quartz看起来更强大些、也支持分布式。但是,但是,但是和@Schedule简单的开发步骤,如果你只是简单的任务,完全用Spring的就可以了。 若是分布式环境,我们一般也不会直接使用quartz,而是选择其它更适合的产品(虽然它有可能是基于quartz定制的~~~)
我们可以把@Schedule看做一个轻量级的Quartz,但它使用起来极其方便

使用推荐配置

默认的,SchedulingConfigurer 使用的也是单线程的方式,如果需要配置多线程,则需要指定 PoolSize

@EnableScheduling
@Configuration
public class ScheduldConfig implements SchedulingConfigurer {// 下面是推荐的配置,当然你也可以简单粗暴的使用它:  开启100个核心线程  足够用了吧// taskRegistrar.setScheduler(Executors.newScheduledThreadPool(100));@Overridepublic void configureTasks(ScheduledTaskRegistrar taskRegistrar) {// 方式一:可议直接使用一个TaskScheduler  然后设置上poolSize等参数即可  (推荐)//ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();//taskScheduler.setPoolSize(10);//taskScheduler.initialize(); // 记得调用啊//taskRegistrar.setTaskScheduler(taskScheduler);// 方式二:ScheduledExecutorService(使用ScheduledThreadPoolExecutor,它继承自JUC的ThreadPoolExecutor,是一个和任务调度相关的线程池)ScheduledThreadPoolExecutor poolExecutor = new ScheduledThreadPoolExecutor(10);taskRegistrar.setScheduler(poolExecutor);// =========示例:这里,我们也可以注册一个任务,一直执行的~~~=========taskRegistrar.addFixedRateTask(() -> System.out.println("执行定时任务1: " + new Date()), 1000);}
}

其实这里给了我们打开了一个思路。通过这我们可以捕获到ScheduledTaskRegistrar,从而我们可以通过接口动态的去改变任务的执行时间、以及对任务的增加、删、改、查等操作,有兴趣的小伙伴可以动手试试

总结

Task在平时业务开发中确实使用非常的广泛,但在分布式环境下,其实已经很少使用Spring自带的定时器了,而使用分布式任务调度框架:Elastic-jobxxl-job

另外说几点使用细节:

  1. 标注@Scheduled注解的方法必须无入数
  2. cron、fixedDelay、fixedRate注解属性必须至少一个
  3. 若在分布式环境(或者集群环境)直接使用Spring的Scheduled,请使用分布式锁或者保证任务的幂等

网上有一个谣言:说@Schedule若发生异常后,后面该任务就死掉了不会再执行了。如果看了本文分析的原理,显然就不攻自破了。结论:抛出异常后任务还是会执行的


关注A哥

AuthorA哥(YourBatman)
个人站点www.yourbatman.cn
E-mailyourbatman@qq.com
微 信fsx641385712
活跃平台
公众号BAT的乌托邦(ID:BAT-utopia)
知识星球BAT的乌托邦
每日文章推荐每日文章推荐

BAT的乌托邦


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部