@Scheduled定时

目录

一、项目中常遇到的坑

二、线程池相关内容

2.1 、线程池的创建:

2.1.1  Java通过Executors提供四种线程池

三、 @Scheduled的工作机制,源码分析

1、@Scheduled简单说明

2、ScheduledAnnotationBeanPostProcessor类

3、ScheduledTaskRegistrar类

4、ConcurrentTaskScheduler implements TaskScheduler

5、再看ReschedulingRunnable

定时任务解析流程:

四、重要类的分析:

1、TaskScheduler:

2、ScheduledExecutorService :

3、ScheduledThreadPoolExecutor:

五、定时任务测试

六、使用@Scheduled的经验总结:



一、项目中常遇到的坑

1、定时任务没有按照设置的定时时间执行

2、某个定时任务执行出现执行时间过长的情况时会阻塞其他定时任务的执行

3、定时任务中抛出了异常

我的疑问,或者说我希望看到的点:

源码中如何实现定时任务的单线程的?

源码中如何实现一个定时任务在不同的时间点执行的?是通过队列吗?队列如何实现的呢?

 

二、线程池相关内容

2.1 、线程池的创建:

2.1.1  Java通过Executors提供四种线程池

分别为: 

1)(ThreadPoolExecutor)ExecutorService newCachedThreadPool() : 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。 

2)(ThreadPoolExecutor)ExecutorService newFixedThreadPool(int nThreads) :  创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。 

3)(ThreadPoolExecutor)ExecutorService newSingleThreadExecutor() :  创建只有一个单线程的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

4)(ScheduledThreadPoolExecutor)ScheduledExecutorService newScheduledThreadPool(int corePoolSize) : 创建一个定长线程池,支持定时及周期性任务执行。 corePoolSize是指线程池中保持的线程数,即使线程是空闲的也会被保留在线程池内。

5)(DelegatedScheduledExecutorService)ScheduledExecutorService newSingleThreadScheduledExecutor(): 单线程的线程池,用来支持定时及周期性任务执行。

6)(ForkJoinPool)ExecutorService newWorkStealingPool(int parallelism):创建持有足够的线程的线程池来支持给定的并行级别(parallelism),该方法还会使用多个队列来减少竞争。

7)(ForkJoinPool)ExecutorService newWorkStealingPool():同上, 默认parallelism=Runtime.getRuntime().availableProcessors(), 即系统可用的处理器的个数。

说明:

0)都是接口,继承关系: ScheduledExecutorService extends ExecutorService extends Executor

1) 前三个返回的类型是 ThreadPoolExecutor(是ExecutorService的实现类),其一个构造方式:

// keepAliveTime : 如果线程的数量超过了corePoolSize, 这个数字代表这些线程被移出线程池所需等待的最大时间

// unit : 代表keepAliveTime的时间单位

// threadFactory : executor创建线程池时使用的工厂, 这是默认的Executors.defaultThreadFactory()

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

2) 第四个返回的类型是ScheduledThreadPoolExecutor

3)第五个返回的类型是DelegatedScheduledExecutorService

3) 六和七返回的类型是ForkJoinPool :用来执行ForkJoinTask

 

2、代码中创建一个线程池的代码:

public class ExecutorHolder {

    private static final int DEFAULT_CORE_POOL_SIZE = 8;



    private static final int DEFAULT_MAXIMUM_POOL_SIZE = 32;



    private static final int DEFAULT_QUEUE_SIZE = 256;

    /** 线程池 */

    private static final ExecutorService executor;



    static {

        executor = new ThreadPoolExecutor(DEFAULT_CORE_POOL_SIZE, DEFAULT_MAXIMUM_POOL_SIZE,

                0L, TimeUnit.MILLISECONDS,

                new LinkedBlockingQueue<>(DEFAULT_QUEUE_SIZE));

    }



    /**

     * @return the executor

     */

    public static ExecutorService getExecutor() {

        return executor;

    }

}

 

三、 @Scheduled的工作机制,源码分析

 

1、@Scheduled简单说明

现看下源码:

/**

 * An annotation that marks a method to be scheduled. Exactly one of

 * the {@link #cron()}, {@link #fixedDelay()}, or {@link #fixedRate()}

 * attributes must be specified.

 *

 * <p>The annotated method must expect no arguments. It will typically have

 * a {@code void} return type; if not, the returned value will be ignored

 * when called through the scheduler.

 *

 * <p>Processing of {@code @Scheduled} annotations is performed by

 * registering a {@link ScheduledAnnotationBeanPostProcessor}. This can be

 * done manually or, more conveniently, through the {@code <task:annotation-driven/>}

 * element or @{@link EnableScheduling} annotation.

 *

 * <p>This annotation may be used as a <em>meta-annotation</em> to create custom

 * <em>composed annotations</em> with attribute overrides.





 * @see EnableScheduling

 * @see ScheduledAnnotationBeanPostProcessor

 * @see Schedules

 */

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})

@Retention(RetentionPolicy.RUNTIME)

@Documented

@Repeatable(Schedules.class)

public @interface Scheduled {

}

 

说明:

标注定时的方式有三种:cron() 、 fixedDelay()、fixRate()

被Scheduled注解的方法不能有参数, 返回类型应该是个void(The annotated method must expect no arguments. It will typically have a {@code void} return type)

注解中使用了Schedulers这个类。

处理@Scheduled的方式是通过 注册一个ScheduledAnnotationBeanPostProcessor实现的,

如何实现注册这个定时后处理器呢,是通过在配置文件中配置<task:annotation-driven/>或者添加注解EnableScheduling实现,

所以如何使@Scheduled注解生效呢,即在配置文件中配置<task:annotation-driven/>或者添加注解EnableScheduling。

思考:ScheduledAnnotationBeanPostProcessor中做了哪些工作, 看下面这个类的说明。

 

2、ScheduledAnnotationBeanPostProcessor类

/**

 * Bean post-processor that registers methods annotated with @{@link Scheduled}

 * to be invoked by a {@link org.springframework.scheduling.TaskScheduler} according

 * to the "fixedRate", "fixedDelay", or "cron" expression provided via the annotation.

 *

 * <p>This post-processor is automatically registered by Spring's

 * {@code <task:annotation-driven>} XML element, and also by the

 * {@link EnableScheduling @EnableScheduling} annotation.

 *

 * <p>Autodetects any {@link SchedulingConfigurer} instances in the container,

 * allowing for customization of the scheduler to be used or for fine-grained

 * control over task registration (e.g. registration of {@link Trigger} tasks.

 * See the @{@link EnableScheduling} javadocs for complete usage details.

 *

 * @see Scheduled

 * @see EnableScheduling

 * @see SchedulingConfigurer

 * @see org.springframework.scheduling.TaskScheduler

 * @see org.springframework.scheduling.config.ScheduledTaskRegistrar

 * @see AsyncAnnotationBeanPostProcessor

*/

public class ScheduledAnnotationBeanPostProcessor

      implements ScheduledTaskHolder, MergedBeanDefinitionPostProcessor, DestructionAwareBeanPostProcessor,

      Ordered, EmbeddedValueResolverAware, BeanNameAware, BeanFactoryAware, ApplicationContextAware,

      SmartInitializingSingleton, ApplicationListener<ContextRefreshedEvent>, DisposableBean {   

    

}

说明:

ScheduledAnnotationBeanPostProcessor继承了BeanPostProcessor, 他作为一个后处理器,注册 被@Scheduled标注的方法, 这些方法会被TaskScheduler调度,调度的时间按照定时设置的定时方式去运行。

思考:TaskScheduler时如何调度注册好了的这些@Scheduled方法呢?

先看ScheduledAnnotationBeanPostProcessor类中是如何后处理的:


@Override

public Object postProcessAfterInitialization(Object bean, String beanName) {

   if (bean instanceof AopInfrastructureBean || bean instanceof TaskScheduler ||

         bean instanceof ScheduledExecutorService) {

      // Ignore AOP infrastructure such as scoped proxies.

      return bean;

   }



   Class<?> targetClass = AopProxyUtils.ultimateTargetClass(bean);

   if (!this.nonAnnotatedClasses.contains(targetClass)) {

      //获取含有@Scheduled注解的方法

      Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,

            (MethodIntrospector.MetadataLookup<Set<Scheduled>>) method -> {

               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: " + targetClass);

         }

      }

      else {

         // 循环处理包含@Scheduled注解的方法

         annotatedMethods.forEach((method, scheduledMethods) ->

               scheduledMethods.forEach(scheduled -> processScheduled(scheduled, method, bean)));

         if (logger.isTraceEnabled()) {

            logger.trace(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +

                  "': " + annotatedMethods);

         }

      }

   }

   return bean;

}





/**

 * Process the given {@code @Scheduled} method declaration on the given bean.

 * @param scheduled the @Scheduled annotation

 * @param method the method that the annotation has been declared on

 * @param bean the target bean instance

 * @see #createRunnable(Object, Method)

 */

protected void processScheduled(Scheduled scheduled, Method method, Object bean) {

   Runnable runnable = createRunnable(bean, method);



   Set<ScheduledTask> tasks = new LinkedHashSet<>(4);

   。。。。。。

   tasks.add(this.registrar.scheduleCronTask(new CronTask(runnable, new CronTrigger(cron, timeZone))));

   。。。。。。

   tasks.add(this.registrar.scheduleFixedDelayTask(new FixedDelayTask(runnable, fixedDelay, initialDelay)));

   。。。。。。

   tasks.add(this.registrar.scheduleFixedRateTask(new FixedRateTask(runnable, fixedRate, initialDelay)));

   。。。。。。

   synchronized (this.scheduledTasks) {

   Set<ScheduledTask> regTasks = this.scheduledTasks.computeIfAbsent(bean, key -> new LinkedHashSet<>(4));

   regTasks.addAll(tasks);

   }

}

private final ScheduledTaskRegistrar registrar;

说明:

postProcessAfterInitialization将拦截所有以@Scheduled注解标注的方法,并循环处理这些方法。

如何处理呢?processScheduled方法 获取Scheduled类型参数,之后根据参数类型、相应的延迟时间、对应的时区将定时任务放入不同的任务列表中。在加入任务列表时同时对定时任务进行注册。

ScheduledTaskRegistrar这个类为Spring容器定时任务注册中心

 

3、ScheduledTaskRegistrar类

/**

 * Helper bean for registering tasks with a {@link TaskScheduler}, typically using cron

 * expressions.

 *

 * <p>As of Spring 3.1, {@code ScheduledTaskRegistrar} has a more prominent user-facing

 * role when used in conjunction with the @{@link

 * org.springframework.scheduling.annotation.EnableAsync EnableAsync} annotation and its

 * {@link org.springframework.scheduling.annotation.SchedulingConfigurer

 * SchedulingConfigurer} callback interface.



 * @see org.springframework.scheduling.annotation.EnableAsync

 * @see org.springframework.scheduling.annotation.SchedulingConfigurer

 */

public class ScheduledTaskRegistrar implements ScheduledTaskHolder, InitializingBean, DisposableBean {



   @Nullable

   private TaskScheduler taskScheduler;



   @Nullable

   private ScheduledExecutorService localExecutor;



   @Nullable

   private List<TriggerTask> triggerTasks;



   @Nullable

   private List<CronTask> cronTasks;



   @Nullable

   private List<IntervalTask> fixedRateTasks;



   @Nullable

   private List<IntervalTask> fixedDelayTasks;



   private final Map<Task, ScheduledTask> unresolvedTasks = new HashMap<>(16);



   private final Set<ScheduledTask> scheduledTasks = new LinkedHashSet<>(16);





。。。。。。。





/**

 * Schedule the specified cron task, either right away if possible

 * or on initialization of the scheduler.

 * @return a handle to the scheduled task, allowing to cancel it

 * (or {@code null} if processing a previously registered task)

 * @since 4.3

 */

@Nullable

public ScheduledTask scheduleCronTask(CronTask task) {

   ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);

   boolean newTask = false;

   if (scheduledTask == null) {

      scheduledTask = new ScheduledTask(task);

      newTask = true;

   }

   if (this.taskScheduler != null) {

      scheduledTask.future = this.taskScheduler.schedule(task.getRunnable(), task.getTrigger());

   }

   else {

      addCronTask(task);

      this.unresolvedTasks.put(task, scheduledTask);

   }

   return (newTask ? scheduledTask : null);

}

}

 

说明:

ScheduledTaskRegistrar类中在处理定时任务时会调用scheduleCronTask方法初始化定时任务。目的就是为了把定时任务扔给立即taskScheduler去执行(注意,这样说并不代表taskScheduler一定立即运行注解标注的方法),如果taskScheduler为空的话,就把这个任务对象放到集合中。

ScheduledTaskRegistrar类实现了InitializingBean接口!

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

而ScheduledTaskRegistrar类中的该方法如下: 


@Override

public void afterPropertiesSet() {

   scheduleTasks();

}

/**

 * Schedule all registered tasks against the underlying

 * {@linkplain #setTaskScheduler(TaskScheduler) task scheduler}.

 */

@SuppressWarnings("deprecation")

protected void scheduleTasks() {

   if (this.taskScheduler == null) {

      this.localExecutor = Executors.newSingleThreadScheduledExecutor();

      this.taskScheduler = new ConcurrentTaskScheduler(this.localExecutor);

   }

   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));

      }

   }

}

说明:

ScheduledTaskRegistrar在使用taskScheduler执行定时任务时,taskScheduler使用的线程池是单线程!(如何没有指定线程池的情况下,项目中所有使用@Scheduled注解的方法在调度执行的时候都是使用的一个线程!)

4、ConcurrentTaskScheduler implements TaskScheduler

最终定时任务交给ConcurrentTaskScheduler来执行

@Override

@Nullable

public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {

   try {

      if (this.enterpriseConcurrentScheduler) {

         return new EnterpriseConcurrentTriggerScheduler().schedule(decorateTask(task, true), trigger);

      }

      else {

         ErrorHandler errorHandler =

               (this.errorHandler != null ? this.errorHandler : TaskUtils.getDefaultErrorHandler(true));

         return new ReschedulingRunnable(task, trigger, this.scheduledExecutor, errorHandler).schedule();

      }

   }

   catch (RejectedExecutionException ex) {

      throw new TaskRejectedException("Executor [" + this.scheduledExecutor + "] did not accept task: " + task, ex);

   }

}

 

5、再看ReschedulingRunnable

/**

 * Internal adapter that reschedules an underlying {@link Runnable} according

 * to the next execution time suggested by a given {@link Trigger}.

 *

 * <p>Necessary because a native {@link ScheduledExecutorService} supports

 * delay-driven execution only. The flexibility of the {@link Trigger} interface

 * will be translated onto a delay for the next execution time (repeatedly).

 *

 * @author Juergen Hoeller

 * @author Mark Fisher

 * @since 3.0

 */

class ReschedulingRunnable extends DelegatingErrorHandlingRunnable implements ScheduledFuture<Object> {

     

   private final Trigger trigger;

   private final ScheduledExecutorService executor;





   public ReschedulingRunnable( Runnable delegate, Trigger trigger, ScheduledExecutorService executor, ErrorHandler errorHandler) {

   super(delegate, errorHandler);

   this.trigger = trigger;

   this.executor = executor;

   }

    

   @Nullable

   public ScheduledFuture<?> schedule() {

   synchronized (this.triggerContextMonitor) {

      this.scheduledExecutionTime = this.trigger.nextExecutionTime(this.triggerContext);

      if (this.scheduledExecutionTime == null) {

         return null;

      }

      long initialDelay = this.scheduledExecutionTime.getTime() - System.currentTimeMillis();

      this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);

      return this;

   }

  }



}

说明:

通过trigger计算下次要执行的时间,和当前系统的时间比对,延迟initialDelay这么个时间来执行。

由此也可以看出,如果将系统时间改了,当Spring将之前获取的基准时间(程序计算的代码将要执行的时间点)与当下获取的系统时间进行比对时,就有可能造成Spring内部定时任务的失效。

定时任务解析流程:

 

四、重要类的分析:

1、TaskScheduler:

这个接口抽象了任务的调度,简单说,就是用来进行任务调度的。常见的直接实现类有:

  • ConcurrentTaskScheduler
  • ThreadPoolTaskScheduler
  • DefaultManagedTaskScheduler

其中ThreadPoolTaskScheduler是其默认实现方式。(不理解为什么这么说)

TaskScheduler既然是任务调度,就要包含 线程(线程池)+ 任务,

比如ConcurrentTaskScheduler的构造器如下:

private Executor concurrentExecutor;

private TaskExecutorAdapter adaptedExecutor;

private ScheduledExecutorService scheduledExecutor;

private boolean enterpriseConcurrentScheduler = false;





// 默认都是创建的单线程的线程池

public ConcurrentTaskScheduler() {

   。。。

   this.scheduledExecutor = Executors.newSingleThreadScheduledExecutor();

}

public ConcurrentTaskScheduler(ScheduledExecutorService scheduledExecutor){

   。。。

   this.scheduledExecutor = scheduledExecutor;

}

public ConcurrentTaskScheduler(Executor concurrentExecutor, ScheduledExecutorService scheduledExecutor) {

   super(concurrentExecutor);

   this.scheduledExecutor = initScheduledExecutor(scheduledExecutor);

}

另外ThreadPoolTaskScheduler中的方法,不能直接调用。

protected ExecutorService initializeExecutor(ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) : 是创建的单线程的线程池,使用默认的线程数1

protected ScheduledExecutorService createExecutor(
int poolSize, ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler)

 

可以通过下面这种方式创建:

@Bean
public TaskScheduler taskScheduler() {
    ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
    threadPoolTaskScheduler.setPoolSize(10); // 设置了这个任务执行器里面对应的线程池的线程数
    threadPoolTaskScheduler.setThreadNamePrefix("ThreadPoolTaskScheduler-");
    return threadPoolTaskScheduler;
}

 

2、ScheduledExecutorService :

继承ExecutorService接口的接口,用来在指定的时间或周期性的执行命令,是个线程池

主要方法:

ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit)

<V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)

ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)

ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay, TimeUnit unit)

其常见的实现类ScheduledThreadPoolExecutor

3、ScheduledThreadPoolExecutor:

ScheduledThreadPoolExecutor,它任务的调度是基于相对时间的,原因是它在任务的内部 存储了该任务距离下次调度还需要的时间(使用的是基于 System.nanoTime实现的相对时间不会因为系统时间改变而改变,如距离下次执行还有10秒,不会因为将系统时间调前6秒而变成4秒后执行)。

创建个指定线程数量的ScheduledThreadPoolExecutor的方式是:

ScheduledThreadPoolExecutor object = Executors.newScheduledThreadPool(int corePoolSize)

通过指定线程池创建一个TaskScheduler

public TaskScheduler taskScheduler() {
 ScheduledThreadPoolExecutor object = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(10);
 ConcurrentTaskScheduler concurrentTaskScheduler = new ConcurrentTaskScheduler(object);
 return concurrentTaskScheduler;
}

五、定时任务测试

简单的一个定时设置,每5s打印一下内容

1—4 :任务实行的时间 不会超过 定时的时间间隔 的情况:

1、程序正常情况下,没有指定线程池时,只有一个默认的线程在运行。

2、程序正常情况下,指定线程池,并且线程池中有5个线程,指定线程名字的前缀ThreadPollTaskScheduler-:

 

3、程序抛出异常时,没有指定线程池(单线程):

-----MySchedulerTest doJob, time=Thu Feb 27 12:57:15 CST 2020, threadName=pool-10-thread-1
java.lang.ArithmeticException: / by zero
-----MySchedulerTest doJob, time=Thu Feb 27 12:57:20 CST 2020, threadName=pool-10-thread-1
java.lang.ArithmeticException: / by zero
-----MySchedulerTest doJob, time=Thu Feb 27 12:57:25 CST 2020, threadName=pool-10-thread-1
java.lang.ArithmeticException: / by zero
-----MySchedulerTest doJob, time=Thu Feb 27 12:57:30 CST 2020, threadName=pool-10-thread-1
java.lang.ArithmeticException: / by zero
-----MySchedulerTest doJob, time=Thu Feb 27 12:57:35 CST 2020, threadName=pool-10-thread-1
java.lang.ArithmeticException: / by zero

可以发现,定时任务仍然再定时的执行,并没有因为一次执行有异常抛出,导致接下来的定时都没有执行(这是我之前的一个误解)

4、程序抛出异常时,指定线程池(多线程):
-----MySchedulerTest doJob, time=Thu Feb 27 12:39:25 CST 2020, threadName=ThreadPoolTaskScheduler-1
java.lang.ArithmeticException: / by zero
-----MySchedulerTest doJob, time=Thu Feb 27 12:39:30 CST 2020, threadName=ThreadPoolTaskScheduler-2
java.lang.ArithmeticException: / by zero
-----MySchedulerTest doJob, time=Thu Feb 27 12:39:35 CST 2020, threadName=ThreadPoolTaskScheduler-3
java.lang.ArithmeticException: / by zero
-----MySchedulerTest doJob, time=Thu Feb 27 12:39:40 CST 2020, threadName=ThreadPoolTaskScheduler-2
java.lang.ArithmeticException: / by zero
-----MySchedulerTest doJob, time=Thu Feb 27 12:39:45 CST 2020, threadName=ThreadPoolTaskScheduler-2
java.lang.ArithmeticException: / by zero
-----MySchedulerTest doJob, time=Thu Feb 27 12:39:50 CST 2020, threadName=ThreadPoolTaskScheduler-2
java.lang.ArithmeticException: / by zero
-----MySchedulerTest doJob, time=Thu Feb 27 12:39:55 CST 2020, threadName=ThreadPoolTaskScheduler-2

可以发现,多线程执行定时任务的时候,也没有因为一次线程抛出异常,导致这个线程不能被再次拿出来执行下次的定时任务。

(我理解的是这个是线程池的工作机制,线程被中断后,这个线程应该归还给线程池,至于具体的过程还没有仔细了解)

5—6、任务实行的时间 超过 定时的时间间隔 的情况:

5、设置任务执行的时候休眠10s, 没有指定线程池(单线程)

会发下每隔15s才执行一次,这种也可以理解,就是任务的执行(当前就是打印一句话)也需要时间,加上任务的调度等等,会导致这个任务的实际执行的时间超过10s,所以本来该重新定时任务执行的时间点,由于上个任务还没有执行完,所以只能再往后推5s,变成了15s执行一次, 为了更清晰的说明,可以设置休眠时间为7s,结果如下:

6、设置任务执行的时候休眠7s, 指定线程池(多线程)

上面的测试中,项目中只有一个定时任务,我只在一个文件中写了@Scheduled,如果我在多个文件中都写了@Scheduled, 或者在一个文件中写多个@Scheduled,运行方式是什么样的呢?不过所有@Scheduled注解的方法被处理的时候,走的都是一个线程池。曾经的积分项目也验证过这一点。

7、一个文件有两个@Scheduled, 都是@Scheduled(cron = "0/5 * * * * ?"), 没有指定线程池(单线程),任务执行时只是打印一句话, 发现每个任务都是按照设置的定时时间每隔5s执行一次。

对上面这个还感到比较疑惑,会想为什么一个线程在同一时间处理了两个任务呢?把任务的执行时间设置长一点,设置每个任务睡眠1s,执行结果就很不一样

 

从上面可以看出doTask任务和doJob任务有的是在0、5点执行的(这种符合定时的时间),有的是在1、6点执行的,这是为什么呢?

区分 定时时间点 和 计算的任务下一个执行时间点 和 实际任务下一个时间点

按照定时规则和任务的执行时间,计算出的doTask和doJob任务的下一个执行时间点都是0或5,但是只有一个线程,所以只能一个任务被先执行,一个被后者执行,后执行的任务的实际执行时间点就是1、6了。(前提是两个任务都到了执行时间点,只是缺少线程)

单@Scheduled的情况, 如果一个任务的执行时间过长,导致定时的点没有被执行,因为定时的时间点 != 计算的任务下一个执行时间点。任务执行完成后根据当前时间和定时规则计算下一个任务执行时间点。

在单个Scheduled的情况下,无论多线程单线程,任务的实际执行时间点都是符合定时规则的。只有在多个Scheduled的情况下,才会存在任务实际执行的时间不符合定时规则。

 

8、多个文件有多个@Scheduled

情况等同于一个文件多个@Scheduled

 

六、使用@Scheduled的经验总结:

1、要指定任务调度的线程池,否则就是单线程,项目中一般不可能只有一个@Scheduled,再加上如果定时时间设置的格式相似的话,最后会发现定时任务 不执行或者不按照设置的时间执行 这种问题。

2、防止定时任务中的死循环或者长时间的耗时(比如调用其他系统接口,没有设置超时时间,一直等待返回结果),否则即使使用线程池,最后所有的线程都会被拖死。(这种情况有时会重启下,这样返回定时又能执行了,但是重启并不能解决问题的)

3、不要随意更改系统的时间。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值