在用spring自带的定时任务编写业务代码时,只需要在类上面加上@Scheduled注解并配上cron表达式即可完成一个定时任务的开发,那么这个注解的背后原理是什么样的呢?

其实spring并没有自创什么定时任务处理器,它也是用的java提供的ScheduledThreadPoolExecutor这个类实现的定时任务,所以搞清楚这个类的实现原理,也就明白了spring的定时任务的实现原理

首先看一下ScheduledThreadPoolExecutor的继承关系

spring定时器下次执行时间 spring定时器原理_spring

可以看到这个类本身就是一个ThreadPoolExecutor,这和我们平时使用线程池去异步执行任务的方式是一样的,也就是说我们的定时器也是通过一个线程池、任务队列来实现我们的定时任务执行的。但是ScheduledThreadPoolExector和ThreadPoolExecutor还是有不同的地方,这个不同的地方也就是定时器和异步执行任务的执行方式上的区别。

我们先看一下如何创建一个ScheduledThreadPoolExector的:

public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

这是其中的一个构造函数,可以看到这个构造函数实际上也是调用的父类的构造函数,点击去看一下:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

可以看到,这其实就是ThreadPoolExecutor的构造函数,只不过是它的任务队列是用的DelayedWorkQueue,也就是说ScheduledThreadPoolExector和ThreadPoolExecutor的最大区别也就ScheduledThreadPoolExector用DelayedWorkQueue作为其任务队列,那么只需要研究DelayedWorkQueue这个队列其实就明白了spring的定时任务的实现逻辑了,因为其他的内容就和我们用线程池构建异步任务是一样的。

DelayedWorkQueue只接受RunnableScheduledFuture类型的任务,在ScheduledThreadPoolExector里面只有ScheduledFutureTask类实现了RunnableScheduledFuture接口,所以可以认为ScheduledThreadPoolExector默认就是接受ScheduledFutureTask对象。可以看一下RunnableScheduledFuture的定义

public interface RunnableScheduledFuture<V> extends RunnableFuture<V>, ScheduledFuture<V> {

    /**
     * Returns {@code true} if this task is periodic. A periodic task may
     * re-run according to some schedule. A non-periodic task can be
     * run only once.
     *
     * @return {@code true} if this task is periodic
     */
    boolean isPeriodic();
}

可以看到这个接口里面有一个方法:isPeriodic 这个方法告诉执行器该任务是否是定时任务类型的任务,而ScheduledFutureTask类的这个方法,代码如下:

/**
         * Returns {@code true} if this is a periodic (not a one-shot) action.
         *
         * @return {@code true} if periodic
         */
        public boolean isPeriodic() {
            return period != 0;
        }

再看一下这个类的run方法:

/**
         * Overrides FutureTask version so as to reset/requeue if periodic.
         */
        public void run() {
            boolean periodic = isPeriodic();
            if (!canRunInCurrentRunState(periodic))
                cancel(false);
            else if (!periodic)
                ScheduledFutureTask.super.run();
            else if (ScheduledFutureTask.super.runAndReset()) {
                setNextRunTime();
                reExecutePeriodic(outerTask);
            }
        }
    }

上面两个方法说明了当提交任务的时候,如果period参数不为0,也就是任务执行间隔时间不为0的时候表示该任务就是一个定时执行类型的任务,就会走reExecutePeriodic()这个方法,再看一下这个方法的实现内容:

/**
     * Requeues a periodic task unless current run state precludes it.
     * Same idea as delayedExecute except drops task rather than rejecting.
     *
     * @param task the task
     */
    void reExecutePeriodic(RunnableScheduledFuture<?> task) {
        if (canRunInCurrentRunState(true)) {
            //将当前任务再次加入到队列中去
            super.getQueue().add(task);
            if (!canRunInCurrentRunState(true) && remove(task))
                task.cancel(false);
            else
                ensurePrestart();
        }
    }

也就是说当线程执行了run方法执行任务的时候会判断任务的period是否等于0,如果不为0说明该任务是一个定时执行类型的任务,则会将该任务再次加入到任务队列中去等待下次再接着执行。

看到这我以为spring的定时任务的执行原理就已经完了,但是后面调试的时候发现并没有,调试的时候发现spring在向ScheduledThreadPoolExector注册任务的时候用的是java.util.concurrent.ScheduledThreadPoolExecutor#schedule(java.lang.Runnable, long, java.util.concurrent.TimeUnit)这个方法,定位到这个方法里面去看:

public ScheduledFuture<?> schedule(Runnable command,
                                       long delay,
                                       TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        RunnableScheduledFuture<?> t = decorateTask(command,
            new ScheduledFutureTask<Void>(command, null,
                                          triggerTime(delay, unit)));
        delayedExecute(t);
        return t;
    }

注意上面创建ScheduledFutureTask这个对象的时候调用的构造函数是这样的:

/**
         * Creates a one-shot action with given nanoTime-based trigger time.
         */
        ScheduledFutureTask(Runnable r, V result, long ns) {
            super(r, result);
            this.time = ns;
            this.period = 0;
            this.sequenceNumber = sequencer.getAndIncrement();
        }

可以看到这个方法的注释已经说了是创建一个只执行一次的定时任务,因为里面的period=0。那spring注册的任务又是如何重复执行的呢,因为上上面那个构造函数创建的任务只会执行一次。

接着看spring是如何向ScheduledThreadPoolExecutor中注册任务的,代码如下:

public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
		ScheduledExecutorService executor = getScheduledExecutor();
		try {
			ErrorHandler errorHandler = this.errorHandler;
			if (errorHandler == null) {
				errorHandler = TaskUtils.getDefaultErrorHandler(true);
			}
            //这里就是向ScheduledThreadPoolExecuto中注册任务的地方
			return new ReschedulingRunnable(task, trigger, this.clock, executor, errorHandler).schedule();
		}
		catch (RejectedExecutionException ex) {
			throw new TaskRejectedException("Executor [" + executor + "] did not accept task: " + task, ex);
		}
	}

可以看一下这个也是构建了ReschedulingRunnable这样一个runnable,看一下这个类的实现:

class ReschedulingRunnable extends DelegatingErrorHandlingRunnable implements ScheduledFuture<Object> {

    //向ScheduledThreadPoolExecutor注册服务
	@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() - this.triggerContext.getClock().millis();
			this.currentFuture = this.executor.schedule(this, initialDelay, TimeUnit.MILLISECONDS);
			return this;
		}
	}

    //定时任务执行的时候会执行这个run方法
	@Override
	public void run() {
		Date actualExecutionTime = new Date(this.triggerContext.getClock().millis());
		super.run();
		Date completionTime = new Date(this.triggerContext.getClock().millis());
		synchronized (this.triggerContextMonitor) {
			Assert.state(this.scheduledExecutionTime != null, "No scheduled execution");
			this.triggerContext.update(this.scheduledExecutionTime, actualExecutionTime, completionTime);
			if (!obtainCurrentFuture().isCancelled()) {
                //重新向ScheduledThreadPoolExecutor注册服务
				schedule();
			}
		}
	}

}

可以看到,spring还是重新实现了定时任务的重复执行的方式,就是重复向ScheduledThreadPoolExecutor注册服务。