ScheduledExecutorService定时执行任务

目前的 Web 应用,多数应用都具备任务调度的功能。最简单的java任务调度可以用 Java 的Timer

Timer 的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务。

另外一个java提供的比较方便的是:ScheduledExecutor

鉴 于 Timer 的上述缺陷,Java 5 推出了基于线程池设计的 ScheduledExecutor。其设计思想是,每一个被调度的任务都会由线程池中一个线程去执行,因此任务是并发执行的,相互之间不会受到干扰。需 要注意的是,只有当任务的执行时间到来时,ScheduedExecutor 才会真正启动一个线程,其余时间 ScheduledExecutor 都是在轮询任务的状态。

一:简单说明

ScheduleExecutorService接口中有四个重要的方法,其中scheduleAtFixedRate和scheduleWithFixedDelay在实现定时程序时比较方便。

下面是该接口的原型定义

java.util.concurrent.ScheduleExecutorService extends ExecutorService extends Executor

接口scheduleAtFixedRate原型定义及参数说明

111


 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();
        RunnableScheduledFuture<?> t = decorateTask(command,
            new ScheduledFutureTask<Object>(command,
                                            null,
                                            triggerTime(initialDelay, unit),
                                            unit.toNanos(period)));
        delayedExecute(t);
        return t;
    }

command:执行线程
initialDelay:初始化延时
period:前一次执行结束到下一次执行开始的间隔时间(间隔执行延迟时间)
unit:计时单位
------------------

 public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit) {
        if (command == null || unit == null)
            throw new NullPointerException();
        if (delay <= 0)
            throw new IllegalArgumentException();
        RunnableScheduledFuture<?> t = decorateTask(command,
            new ScheduledFutureTask<Boolean>(command,
                                             null,
                                             triggerTime(initialDelay, unit),
                                             unit.toNanos(-delay)));
        delayedExecute(t);
        return t;
    }

 

ScheduledExecutorService 提供时间排程的功能函数

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

创建并执行在给定延迟后启用的 ScheduledFuture。 执行一次

schedule(Runnable command, long delay, TimeUnit unit)

创建并执行在给定延迟后启用的一次性操作。 执行一次

scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnitunit)

创建并执行一个在给定初始延迟后首次启用的定期操作,后续操作具有给定的周期;也就是将在 initialDelay 后开始执行,然后在initialDelay+period 后执行,接着在 initialDelay + 2 * period 后执行,依此类推。

scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit)

创建并执行一个在给定初始延迟后首次启用的定期操作,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟。

例如: scheduleWithFixedDelay(task, 10s, 35s, 秒) 表示, 延迟10秒钟执行task任务, 然后以后每间隔 35秒钟执行下一次任务!!!! 间隔单位可以设置

schedule方法被用来延迟指定时间来执行某个指定任务。如果你需要周期性重复执行定时任务可以使用scheduleAtFixedRate或者scheduleWithFixedDelay方法,它们不同的是前者以固定频率执行,后者以相对固定频率执行。

不管任务执行耗时是否大于间隔时间,scheduleAtFixedRate和scheduleWithFixedDelay都不会导致同一个任务并发地被执行。唯一不同的是scheduleWithFixedDelay是当前一个任务结束的时刻,开始结算间隔时间,如0秒开始执行第一次任务,任务耗时5秒,任务间隔时间3秒,那么第二次任务执行的时间是在第8秒开始。

ScheduledExecutorService 的实现类,是ScheduledThreadPoolExecutor。ScheduledThreadPoolExecutor对象包含的线程数量是没 有可伸缩性的,只会有固定数量的线程。不过你可以通过其构造函数来设定线程的优先级,来降低定时任务线程的系统占用。

特别提示: 通过ScheduledExecutorService执行的周期任务,如果任务执行过程中抛出了异常,那么过 ScheduledExecutorService就会停止执行任务,且也不会再周期地执行该任务了。所以你如果想保住任务都一直被周期执行,那么 catch一切可能的异常。

二:功能示例1.按指定频率周期执行某个任务。

初始化延迟0ms开始执行,每隔100ms重新执行一次任务。

	/**
	 * 以固定周期频率执行任务
	 */
	public static void executeFixedRate() {
		ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
		executor.scheduleAtFixedRate(new EchoServer(), 0, 100,
				TimeUnit.MILLISECONDS);
	}

间隔指的是连续两次任务开始执行的间隔。

请注意这里用的方法是:scheduleAtFixedRate

2.按指定频率间隔执行某个任务。

初始化时延时0ms开始执行,本次执行结束后延迟100ms开始下次执行。

	/**
	 * 以固定延迟时间进行执行 本次任务执行完成后,需要延迟设定的延迟时间,才会执行新的任务
	 */
	public static void executeFixedDelay() {
		ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
		executor.scheduleWithFixedDelay(new EchoServer(), 0, 100,
				TimeUnit.MILLISECONDS);
	}

请注意,这里用的方法是: scheduleWithFixedDelay

3.周期定时执行某个任务。

有时候我们希望一个任务被安排在凌晨3点(访问较少时)周期性的执行一个比较耗费资源的任务,可以使用下面方法设定每天在固定时间执行一次任务。

	/**
	 * 每天晚上8点执行一次 每天定时安排任务进行执行
	 */
	public static void executeEightAtNightPerDay() {
		ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
		long oneDay = 24 * 60 * 60 * 1000;

		long initDelay = getTimeMillis("20:00:00") - System.currentTimeMillis();
		initDelay = initDelay > 0 ? initDelay : oneDay + initDelay;
		executor.scheduleAtFixedRate(new EchoServer(), initDelay, oneDay,
				TimeUnit.MILLISECONDS);
	}

	/**
	 * 获取指定时间对应的毫秒数
	 *
	 * @param time
	 *            “HH:mm:ss”
	 * @return
	 */
	private static long getTimeMillis(String time) {
		try {
			DateFormat dateFormat = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
			DateFormat dayFormat = new SimpleDateFormat("yy-MM-dd");
			Date curDate = dateFormat.parse(dayFormat.format(new Date()) + " "
					+ time);
			return curDate.getTime();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return 0;
	}

	public static class EchoServer implements Runnable {
		@Override
		public void run() {
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("This is a echo server. The current time is "
					+ System.currentTimeMillis() + ".");
		}
	}

三:一些问题

上面写的内容有不严谨的地方,比如对于scheduleAtFixedRate方法,当我们要执行的任务大于我们指定的执行间隔时会怎么样呢?

对于中文API中的注释,我们可能会被忽悠,认为无论怎么样,它都会按照我们指定的间隔进行执行,其实当执行任务的时间大于我们指定的间隔时间时,它并不会在指定间隔时开辟一个新的线程并发执行这个任务。而是等待该线程执行完毕。

源码注释如下:

* Creates and executes a periodic action that becomes enabled first
* after the given initial delay, and subsequently with the given
* period; that is executions will commence after
* <tt>initialDelay</tt> then <tt>initialDelay+period</tt>, then
* <tt>initialDelay + 2 * period</tt>, and so on.
* If any execution of the task
* encounters an exception, subsequent executions are suppressed.
* Otherwise, the task will only terminate via cancellation or
* termination of the executor. If any execution of this task
* takes longer than its period, then subsequent executions
* may start late, but will not concurrently execute.

根据注释中的内容,我们需要注意的时,我们需要捕获最上层的异常,防止出现异常中止执行,导致周期性的任务不再执行。

四:除了我们自己实现定时任务之外,我们可以使用Spring帮我们完成这样的事情。

Spring自动定时任务配置方法(我们要执行任务的类名为com.study.MyTimedTask)

     <bean id="myTimedTask" class="com.study.MyTimedTask" />

    <bean id="doMyTimedTask"
        class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
        <property name="targetObject" ref="myTimedTask" />
        <property name="targetMethod" value="execute" />
        <property name="concurrent" value="false" />
    </bean>

    <bean id="myTimedTaskTrigger"
        class="org.springframework.scheduling.quartz.CronTriggerBean">
        <property name="jobDetail" ref="doMyTimedTask" />
        <property name="cronExpression" value="0 0 2 * ?" />
    </bean>

    <bean id="doScheduler"
        class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <ref local="myTimedTaskTrigger" />
            </list>
        </property>
    </bean>

    <bean id="doScheduler"
        class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
        <property name="triggers">
            <list>
                <bean
                    class="org.springframework.scheduling.quartz.CronTriggerBean">
                    <property name="jobDetail" />
                    <bean id="doMyTimedTask"
                        class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
                        <property name="targetObject">
                            <bean class="com.study.MyTimedTask" />
                        </property>
                        <property name="targetMethod" value="execute" />
                        <property name="concurrent" value="false" />
                    </bean>
        </property>
        <property name="cronExpression" value="0 0 2 * ?" />
    </bean>

参考文章
ScheduledExecutorService定时执行任务
用ScheduledThreadPoolExecutor替换Timer定时执行任务
几种任务调度的 Java 实现方法与比较
在android中,经常用到的定时器主要有以下几种实现

发表评论