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內部的存儲結構爲順序存儲(數組),邏輯結構爲排序二叉樹,排序方式爲任務的執行時間,執行時間越早的排在越前面。

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