Spring Task

前言

在一开始学习定时任务的时候是使用的quartz来实现的。后来习惯于全注解的开发模式。在SpringBoot环境中,只需要在启动类上加上EnableScheduling注解,然后在需要使用定时任务的方法上加上Scheduled注解,当然方法所属的类需要在Spring环境中。在启动类中加上EnableScheduling注解这一步是为了生成ScheduledAnnotationBeanPostProcessor这个BeanPostProccessor实现类,这个类用来对添加了Scheduled注解的方法进行增强处理,调用相应的类来完成定时任务。这一步也可以换成配置一个Configuration类,总之,能把ScheduledAnnotationBeanPostProcessor这个BeanPostProccessor实现类注入到Spring环境中就好。

ScheduledAnnotationBeanPostProcessor

先来看这个关键的BeanPostProccessor实现类,关于BeanPostProccessor起作用的时机就不在赘述了,我们直接看这个类的postProcessAfterInitialization方法

	@Override
	public Object postProcessAfterInitialization(final Object bean, String beanName) {
		Class<?> targetClass = AopUtils.getTargetClass(bean);
		if (!this.nonAnnotatedClasses.contains(targetClass)) {
			Map<Method, Set<Scheduled>> annotatedMethods = MethodIntrospector.selectMethods(targetClass,
					new MethodIntrospector.MetadataLookup<Set<Scheduled>>() {
						@Override
						public Set<Scheduled> inspect(Method 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: " + bean.getClass());
				}
			}
			else {
				// Non-empty set of methods
				//拿到Bean中所有带有@Scheduled注解的方法。
				for (Map.Entry<Method, Set<Scheduled>> entry : annotatedMethods.entrySet()) {
					Method method = entry.getKey();
					for (Scheduled scheduled : entry.getValue()) {
                        //处理注解的方法
						processScheduled(scheduled, method, bean);
					}
				}
				if (logger.isDebugEnabled()) {
					logger.debug(annotatedMethods.size() + " @Scheduled methods processed on bean '" + beanName +
							"': " + annotatedMethods);
				}
			}
		}
		return bean;
	}

processScheduled

	protected void processScheduled(Scheduled scheduled, Method method, Object bean) {
		try {
			Assert.isTrue(method.getParameterTypes().length == 0,
					"Only no-arg methods may be annotated with @Scheduled");

			Method invocableMethod = AopUtils.selectInvocableMethod(method, bean.getClass());
			//将方法的执行封装到一个Runnable实现类的run()方法中
			Runnable runnable = new ScheduledMethodRunnable(bean, invocableMethod);
			boolean processedSchedule = false;
			String errorMessage =
					"Exactly one of the 'cron', 'fixedDelay(String)', or 'fixedRate(String)' attributes is required";

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

			// Determine initial delay
			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);
				}
				try {
					initialDelay = Long.parseLong(initialDelayString);
				}
				catch (NumberFormatException ex) {
					throw new IllegalArgumentException(
							"Invalid initialDelayString value \"" + initialDelayString + "\" - cannot parse into integer");
				}
			}

			// Check cron expression
			String cron = scheduled.cron();
			if (StringUtils.hasText(cron)) {
				Assert.isTrue(initialDelay == -1, "'initialDelay' not supported for cron triggers");
				processedSchedule = true;
				String zone = scheduled.zone();
				if (this.embeddedValueResolver != null) {
					cron = this.embeddedValueResolver.resolveStringValue(cron);
					zone = this.embeddedValueResolver.resolveStringValue(zone);
				}
				TimeZone timeZone;
				if (StringUtils.hasText(zone)) {
					timeZone = StringUtils.parseTimeZoneString(zone);
				}
				else {
					timeZone = TimeZone.getDefault();
				}
				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 anymore
			if (initialDelay < 0) {
				initialDelay = 0;
			}

			// Check fixed delay
			long fixedDelay = scheduled.fixedDelay();
			if (fixedDelay >= 0) {
				Assert.isTrue(!processedSchedule, errorMessage);
				processedSchedule = true;
				tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));
			}
			String fixedDelayString = scheduled.fixedDelayString();
			if (StringUtils.hasText(fixedDelayString)) {
				Assert.isTrue(!processedSchedule, errorMessage);
				processedSchedule = true;
				if (this.embeddedValueResolver != null) {
					fixedDelayString = this.embeddedValueResolver.resolveStringValue(fixedDelayString);
				}
				try {
					fixedDelay = Long.parseLong(fixedDelayString);
				}
				catch (NumberFormatException ex) {
					throw new IllegalArgumentException(
							"Invalid fixedDelayString value \"" + fixedDelayString + "\" - cannot parse into integer");
				}
				tasks.add(this.registrar.scheduleFixedDelayTask(new IntervalTask(runnable, fixedDelay, initialDelay)));
			}

			// Check fixed rate
			long fixedRate = scheduled.fixedRate();
			if (fixedRate >= 0) {
				Assert.isTrue(!processedSchedule, errorMessage);
				processedSchedule = true;
				tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));
			}
			String fixedRateString = scheduled.fixedRateString();
			if (StringUtils.hasText(fixedRateString)) {
				Assert.isTrue(!processedSchedule, errorMessage);
				processedSchedule = true;
				if (this.embeddedValueResolver != null) {
					fixedRateString = this.embeddedValueResolver.resolveStringValue(fixedRateString);
				}
				try {
					fixedRate = Long.parseLong(fixedRateString);
				}
				catch (NumberFormatException ex) {
					throw new IllegalArgumentException(
							"Invalid fixedRateString value \"" + fixedRateString + "\" - cannot parse into integer");
				}
				tasks.add(this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay)));
			}

			// Check whether we had any attribute set
			Assert.isTrue(processedSchedule, errorMessage);

			// Finally register the scheduled tasks
			synchronized (this.scheduledTasks) {
				Set<ScheduledTask> registeredTasks = this.scheduledTasks.get(bean);
				if (registeredTasks == null) {
					registeredTasks = new LinkedHashSet<ScheduledTask>(4);
					this.scheduledTasks.put(bean, registeredTasks);
				}
				registeredTasks.addAll(tasks);
			}
		}
		catch (IllegalArgumentException ex) {
			throw new IllegalStateException(
					"Encountered invalid @Scheduled method '" + method.getName() + "': " + ex.getMessage());
		}
	}

这个方法很长,但是没有难以理解的地方,但是看这个流程有点疑问,当我们在@Scheduled上加了fixedDelay参数和cron参数后,难道会生成多个Task去执行吗?(后续测试一下)
这个方法最重要的地方就是通过registrar注册Task,交给线程池处理。

this.registrar.scheduleFixedRateTask(new IntervalTask(runnable, fixedRate, initialDelay))

这个方法进行分析

registrar对应的是ScheduledTaskRegistrar

	public ScheduledTask scheduleFixedRateTask(IntervalTask task) {
		ScheduledTask scheduledTask = this.unresolvedTasks.remove(task);
		boolean newTask = false;
		if (scheduledTask == null) {
			scheduledTask = new ScheduledTask();
			newTask = true;
		}
		if (this.taskScheduler != null) {
			if (task.getInitialDelay() > 0) {
				Date startTime = new Date(System.currentTimeMillis() + task.getInitialDelay());
				//默认的taskScheduler是
				scheduledTask.future =
						this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), startTime, task.getInterval());
			}
			else {
				scheduledTask.future =
						this.taskScheduler.scheduleAtFixedRate(task.getRunnable(), task.getInterval());
			}
		}
		else {
			addFixedRateTask(task);
			this.unresolvedTasks.put(task, scheduledTask);
		}
		return (newTask ? scheduledTask : null);
	}

经过一系列的委托,最终发现调用的是ScheduledThreadPoolExecutor里的方法。

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

我们看到使用的workQueue与之前分析的线程池都不同,DelayedWorkQueueScheduledThreadPoolExecutor的一个内部类,有着自己特殊的实现。

我们以scheduleAtFixedRate方法为例来分析。

    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        if (period <= 0)
            throw new IllegalArgumentException();
        //将Runnable实现类封装成ScheduledFutureTask(ScheduledThreadPoolExecutor的内部类)
        ScheduledFutureTask<Void> sft =
            new ScheduledFutureTask<Void>(command,
                                          null,
                                          triggerTime(initialDelay, unit),
                                          unit.toNanos(period));
                                          //调用decorateTask方法,其实返回的也是sft,但是类型转化为父类RunnableScheduledFuture
        RunnableScheduledFuture<Void> t = decorateTask(command, sft);
        //设置outerTask为自己
        sft.outerTask = t;
        //延迟执行
        delayedExecute(t);
        return t;
    }

delayedExecute

private void delayedExecute(RunnableScheduledFuture<?> task) {
//如果线程池SHUTDOWN
        if (isShutdown())
            //选择拒绝策略拒绝任务,该任务的执行与否取决于具体的拒绝策略
            reject(task);
        else {
        //先添加到workQueue中
            super.getQueue().add(task);
           //如果线程池SHUTDOWN或者不在RUN状态 就从workQueue移除该任务
            if (isShutdown() &&
                !canRunInCurrentRunState(task.isPeriodic()) &&
                remove(task))
                //如果移除成功,则取消当前任务
                task.cancel(false);
            else
            //否则就可以让当前任务准备执行
                ensurePrestart();
        }
    }

ensurePrestart

    void ensurePrestart() {
        int wc = workerCountOf(ctl.get());
        //保证当前的Worker个数永远不会超过corePoolSize
        if (wc < corePoolSize)
            addWorker(null, true);
        else if (wc == 0)
            addWorker(null, false);
    }

这个方法就是执行addWorker方法
但是一般情况下,是不会去添加的。因为通常来说ScheduledThreadPoolExecutor的核心线程池不会太大(默认使用corePoolSize为1),一旦当前的Worker不小于核心线程池大小之后,就无法添加了。所以后续的任务都会添加到workQueue中。

ScheduledFutureTask

该类是ScheduledThreadPoolExecutor的内部类,继承了FutureTask

    private class ScheduledFutureTask<V>
            extends FutureTask<V> implements RunnableScheduledFuture<V> {
      public void run() {
      
            boolean periodic = isPeriodic();
            if (!canRunInCurrentRunState(periodic))
                cancel(false);
        //如果不是定时任务
            else if (!periodic)
            //调用FutureTask的run方法
                ScheduledFutureTask.super.run();
            //如果是定时任务,调用FutureTask的runAndReset方法,该方法不会设置返回值
            else if (ScheduledFutureTask.super.runAndReset()) {
            //设置下次执行的时间
                setNextRunTime();
                //继续执行这个任务,outerTask指向的仍然是当前任务,又返回到这个run()方法,这样就完成了定时任务
                reExecutePeriodic(outerTask);
            }
        }
        }

setNextRunTime()

    private void setNextRunTime() {
            long p = period;
            if (p > 0)
                time += p;
            else
                time = triggerTime(-p);
        }
    long triggerTime(long delay) {
        return now() +
            ((delay < (Long.MAX_VALUE >> 1)) ? delay : overflowFree(delay));
    }

period大于0的时候,下次执行时间就是time(本次任务执行开始时间)+period

小于0时,则是,当前时间(可以理解为本次任务执行结束时间)加上period的绝对值。

这个方法就解释了fixedRate和fixedDelay的区别了。

    void reExecutePeriodic(RunnableScheduledFuture<?> task) {
        if (canRunInCurrentRunState(true)) {
            super.getQueue().add(task);
            if (!canRunInCurrentRunState(true) && remove(task))
                task.cancel(false);
            else
                ensurePrestart();
        }
    }

和之前delayedExecute方法差不多,先添加任务到workQueue,然后就准备被执行就好了。

经过上面的分析,我们发现ScheduledThreadPoolExecutor中的任务在核心线程池只有corePoolSize的时候,线程池中也最多只能有这么多个线程,后续进来的任务都会放入workQueue中,DelayedWorkQueue内部的存储结构为顺序存储(数组),逻辑结构为排序二叉树,排序方式为任务的执行时间,执行时间越早的排在越前面。

展开阅读全文

没有更多推荐了,返回首页