定时任务是每个业务常见的需求,比如每分钟扫描超时支付的订单,每小时清理一次数据库历史数据,每天统计前一天的数据并生成报表等等。
Java自带的Thread线程类、Timer、ScheduledExecutorService都可以用于实现定时任务。以下对后两种方式进行介绍。
方式1:使用Timer实现定时任务
Timer类简介
java的java.util包下提供了Timer类,可用于指定作为定时任务执行,还是一次性任务执行。jdk8源码中对应的文档注释内容如下,基本含义为:
Timer类可实现在后台线程中完成子线程调度,每一个子线程可以执行指定的任务内容。这些任务可以是一次性的任务、或者是定时任务。
一个Timer对象对应一个后台线程,这个Timer对象的任务就是负责依次执行该对象所拥有的task任务。Timer定时器任务应当快速执行完毕,否则可能会影响后续任务的执行(换言之:当添加并执行多个任务时,前面任务的执行用时和异常将影响到后面任务,这也是Timer实现定时任务方式的缺点所在)。
Timer定时任务的正常结束条件:当对Timer定时器对象的最后一个引用断开,并且所有的任务都已经执行完毕。此时,Timer对象就可以被垃圾回收器回收,但是,这个回收时间是不确定的。默认情况下,Timer——定时任务执行线程并不作为守护线程执行,因此它能够使得应用程序终止。因此,如果开发者想要快速终止定时任务执行线程(Timer对象所对应的线程),可以主动调用Timer类的cancel()方法。
如果Timer定时任务执行线程异常终止,例如:由于Timer对象的`stop()`方法被调用,那么所有对于定时任务的调度操作都会导致IllegalStateException异常,就像定时器的`cancel`方法被调用一样。
Timer是线程安全的,即:多个线程可以共享同一个Timer对象,并且不需要做任何线程同步(synchronized)操作。
但是,Timer类不提供是实时监控保证的,因为它的内部通过`Object.wait(long)`方法调度任务。
Timer定时任务调度类,可以扩展到大量并发计划任务(千应该没有问题)。 在内部,它使用二进制堆表示其任务队列,因此计划任务的成本为O(log n),其中n为并发计划任务的数量。
* background thread. Tasks may be scheduled for one-time execution, or for
* repeated execution at regular intervals.
*
* <p>Corresponding to each <tt>Timer</tt> object is a single background
* thread that is used to execute all of the timer's tasks, sequentially.
* Timer tasks should complete quickly. If a timer task takes excessive time
* to complete, it "hogs" the timer's task execution thread. This can, in
* turn, delay the execution of subsequent tasks, which may "bunch up" and
* execute in rapid succession when (and if) the offending task finally
* completes.
*
* <p>After the last live reference to a <tt>Timer</tt> object goes away
* <i>and</i> all outstanding tasks have completed execution, the timer's task
* execution thread terminates gracefully (and becomes subject to garbage
* collection). However, this can take arbitrarily long to occur. By
* default, the task execution thread does not run as a <i>daemon thread</i>,
* so it is capable of keeping an application from terminating. If a caller
* wants to terminate a timer's task execution thread rapidly, the caller
* should invoke the timer's <tt>cancel</tt> method.
*
* <p>If the timer's task execution thread terminates unexpectedly, for
* example, because its <tt>stop</tt> method is invoked, any further
* attempt to schedule a task on the timer will result in an
* <tt>IllegalStateException</tt>, as if the timer's <tt>cancel</tt>
* method had been invoked.
*
* <p>This class is thread-safe: multiple threads can share a single
* <tt>Timer</tt> object without the need for external synchronization.
*
* <p>This class does <i>not</i> offer real-time guarantees: it schedules
* tasks using the <tt>Object.wait(long)</tt> method.
*
* <p>Implementation note: This class scales to large numbers of concurrently
* scheduled tasks (thousands should present no problem). Internally,
* it uses a binary heap to represent its task queue, so the cost to schedule
* a task is O(log n), where n is the number of concurrently scheduled tasks.
*
* <p>Implementation note: All constructors start a timer thread.
Timer类:基本方法
构造方法
通过构造方法,可以指定Timer任务调度线程的执行方法(是否以守护线程的方式执行,前面提到:默认情况下,Timer对应的是非守护线程),也可以指定Timer对象的线程名城。
成员方法
Timer类的成员方法如下,比较重要的有:
【1】cancel():取消附属于当前Timer对象的所有定时任务的执行;
【2】schedule():传入TimerTask对象,指定定时任务的执行。
※关于schedule和scheduleAtFixedRate两个方法的区别
简单来讲,scheduleAtFixedRate()方法更注重定时任务的执行频率,即:如果以scheduleAtFixedRate()方法指定的TimeTask定时任务A,那么,在执行期间如果由于其它任务执行时间较长,而导致定时任务A错过了两个执行周期,那么,在其它任务执行完毕之后,定时任务A就会立即执行(2+1=3)次。
而schedule()方法指定的定时任务则不会,它只会按部就班的逐次执行。
相关解析在jdk原码的文档注释中也给出了,如下所示,
* Schedules the specified task for repeated <i>fixed-rate execution</i>,
* beginning at the specified time. Subsequent executions take place at
* approximately regular intervals, separated by the specified period.
*
* <p>In fixed-rate execution, each execution is scheduled relative to the
* scheduled execution time of the initial execution. If an execution is
* delayed for any reason (such as garbage collection or other background
* activity), two or more executions will occur in rapid succession to
* "catch up." In the long run, the frequency of execution will be
* exactly the reciprocal of the specified period (assuming the system
* clock underlying <tt>Object.wait(long)</tt> is accurate). As a
* consequence of the above, if the scheduled first time is in the past,
* then any "missed" executions will be scheduled for immediate "catch up"
* execution.
*
* <p>Fixed-rate execution is appropriate for recurring activities that
* are sensitive to <i>absolute</i> time, such as ringing a chime every
* hour on the hour, or running scheduled maintenance every day at a
* particular time. It is also appropriate for recurring activities
* where the total time to perform a fixed number of executions is
* important, such as a countdown timer that ticks once every second for
* ten seconds. Finally, fixed-rate execution is appropriate for
* scheduling multiple repeating timer tasks that must remain synchronized
* with respect to one another.
*
* @param task task to be scheduled.
* @param firstTime First time at which task is to be executed.
* @param period time in milliseconds between successive task executions.
* @throws IllegalArgumentException if {@code firstTime.getTime() < 0} or
* {@code period <= 0}
* @throws IllegalStateException if task was already scheduled or
* cancelled, timer was cancelled, or timer thread terminated.
* @throws NullPointerException if {@code task} or {@code firstTime} is null
*/
public void scheduleAtFixedRate(TimerTask task, Date firstTime,
long period) {
if (period <= 0)
throw new IllegalArgumentException("Non-positive period.");
sched(task, firstTime.getTime(), period);
}
Timer类:实现定时任务
以下,我们使用Timer定时任务调度类实现定时任务,示例代码如下,
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class Timer_Test {
//properties
//methods
public static void main(String[] args) throws ParseException {
//创建Timer对象
Timer timer = new Timer("Timer tasks schedule instance");
//执行定时任务
//1-延迟3秒执行任务
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("TimerTask-1");
}
},1000*3);
//2-指定线程任务在何时被一次性执行
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date parse = simpleDateFormat.parse("2022-12-04 22:45:00");
System.out.println(parse);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("定时任务在 "+simpleDateFormat.format(parse)+" 被执行");
}
},parse);
//3-从指定时间开始,以固定延迟时间3s持续执行
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("定时任务在 "+simpleDateFormat.format(parse)+" 被执行[延迟执行]");
}
},parse,1000*3);
//4-从指定时间开始,以固定延迟时间3s持续执行
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
System.out.println("[scheduleAtFixedRate]定时任务在 "+simpleDateFormat.format(parse)+" 被执行[延迟执行]");
}
},parse,1000*3);
}
}
执行效果如下,程序一旦启动之后就会进入阻塞状态,从而在指定周期结束时去执行指定的任务。
TimerTask类
TimerTask类是Runnable接口的子类,用于表示可以由计时器进行一次性或重复执行的任务。其内部维护了lock锁对象,以线程同步的方式来保证任务的安全执行。
其构造函数和成员方法如下,
在使用构造函数创建TimerTask对象时,需要重写run()方法,如下,
Timer timer = new Timer("Timer tasks schedule instance");
//执行定时任务
//1-延迟3秒执行任务
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("TimerTask-1");
}
},1000*3);
方式2:使用ScheduledExecutorService实现定时任务
在jdk8-Timer类的源码文档注释中,也提供了如下的内容,主要含义为:
Java 5.0引入了java.util.concurrent并发编程工具包,以及并发工具java.util.concurrent.ScheduledThreadPoolExecutor,该类表示一个线程池,用于定期或者延迟执行任务。ScheduleThreadPoolExecutor类是Timer+TimerTask组合实现定时任务的替代品,因为该类允许多个服务线程,接受各种时间单位,并且不需要子类TimerTask (只实现Runnable )。所以,只需要配置一个ScheduleThreadPoolExecutor类就等同于Timer类。
* one of the concurrency utilities therein is the {@link
* java.util.concurrent.ScheduledThreadPoolExecutor
* ScheduledThreadPoolExecutor} which is a thread pool for repeatedly
* executing tasks at a given rate or delay. It is effectively a more
* versatile replacement for the {@code Timer}/{@code TimerTask}
* combination, as it allows multiple service threads, accepts various
* time units, and doesn't require subclassing {@code TimerTask} (just
* implement {@code Runnable}). Configuring {@code
* ScheduledThreadPoolExecutor} with one thread makes it equivalent to
* {@code Timer}.
ScheduledExecutorService接口介绍
ScheduledExecutorService接口继承了ExecutorService类(ExecutorService类是jdk内置的一个线程池类,它可以有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞,同时提供定时执行、定期执行、单线程、并发数控制等功能),jdk8的源码中文档注释内容如下,主要含义为:
ExecutorService可以在给定延迟时间之后,或者周期性的调度(线程执行)命令。
ScheduledExecutorService接口中的schedule()方法可以创建任务,并给定不同的延迟时间;并且将创建好的任务对象返回;通过这个对象可以实现任务的取消或者检查执行。
ScheduledExecutorService接口中的scheduleAtFixedRate()方法和scheduleWithFixedDelay()方法可创建一个周期性执行的任务,直到该任务被主动取消。
使用Executor的execute(Runnable runnable)方法、ExecutorService的submit()方法提交的命令以零延迟的方式进行调度。并且,零和负数的延迟时间也被schedule()方法所允许,并且被视为立即执行的任务。
所有的schedule()方法接收一个相对延迟和时间跨度作为参数,而非绝对时间或者日期。因此,在指定延迟时间时,可以采用方式schedule(task, date.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS)进行指定。
Executors类为此包中提供的ScheduledExecutorService实现提供了方便的工厂方法。
* An {@link ExecutorService} that can schedule commands to run after a given
* delay, or to execute periodically.
*
* <p>The {@code schedule} methods create tasks with various delays
* and return a task object that can be used to cancel or check
* execution. The {@code scheduleAtFixedRate} and
* {@code scheduleWithFixedDelay} methods create and execute tasks
* that run periodically until cancelled.
*
* <p>Commands submitted using the {@link Executor#execute(Runnable)}
* and {@link ExecutorService} {@code submit} methods are scheduled
* with a requested delay of zero. Zero and negative delays (but not
* periods) are also allowed in {@code schedule} methods, and are
* treated as requests for immediate execution.
*
* <p>All {@code schedule} methods accept <em>relative</em> delays and
* periods as arguments, not absolute times or dates. It is a simple
* matter to transform an absolute time represented as a {@link
* java.util.Date} to the required form. For example, to schedule at
* a certain future {@code date}, you can use: {@code schedule(task,
* date.getTime() - System.currentTimeMillis(),
* TimeUnit.MILLISECONDS)}. Beware however that expiration of a
* relative delay need not coincide with the current {@code Date} at
* which the task is enabled due to network time synchronization
* protocols, clock drift, or other factors.
*
* <p>The {@link Executors} class provides convenient factory methods for
* the ScheduledExecutorService implementations provided in this package.
*
* <h3>Usage Example</h3>
*
* Here is a class with a method that sets up a ScheduledExecutorService
* to beep every ten seconds for an hour:
*
* <pre> {@code
* import static java.util.concurrent.TimeUnit.*;
* class BeeperControl {
* private final ScheduledExecutorService scheduler =
* Executors.newScheduledThreadPool(1);
*
* public void beepForAnHour() {
* final Runnable beeper = new Runnable() {
* public void run() { System.out.println("beep"); }
* };
* final ScheduledFuture<?> beeperHandle =
* scheduler.scheduleAtFixedRate(beeper, 10, 10, SECONDS);
* scheduler.schedule(new Runnable() {
* public void run() { beeperHandle.cancel(true); }
* }, 60 * 60, SECONDS);
* }
* }}</pre>
*
* @since 1.5
* @author Doug Lea
*/
ScheduledExecutorService接口方法
ScheduledExecutorService接口包含四个方法,主要用法在上面一节已经提及。其中:
-ScheduledExecutorService接口中的schedule()方法可以创建指定延迟时间执行的任务;并且将创建好的任务对象返回;通过这个对象可以实现任务的取消或者检查执行。
-ScheduledExecutorService接口中的scheduleAtFixedRate()方法和scheduleWithFixedDelay()方法可创建一个周期性执行的任务,直到该任务被主动取消。两者之间的区别如下图所示,
ScheduledExecutorService接口使用示例
jdk8源码的文档注释中也给出了ScheduledExecutorService接口的使用示例,代码如下,
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorService_Test {
//properties
//methods
public static void main(String[] args) throws ParseException {
//日期格式化类对象
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date parse = simpleDateFormat.parse("2022-12-04 23:53:30");
long initDelay = (long) ((parse.getTime() - System.currentTimeMillis())/1000.0);
System.out.println("initDelay="+initDelay);
//使用Executors工具类提供的方法创建核心线程容量为5的ScheduledExecutorService接口对象
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5);
//创建Runnable接口对象
Runnable runnable_fixedRate = ()->{
System.out.println("[runnable]定时任务延迟3s执行...");
};
Runnable runnable_regularly = ()->{
System.out.println("[runnable]定时任务周期性执行-【首次执行时间:2022-12-04 23:47:30】...");
};
//配置定时任务
//1指定延迟3秒执行定时任务
executorService.schedule(runnable_fixedRate,3, TimeUnit.SECONDS);
//2-指定周期性执行的定时任务-[配置:首次执行的时间;首次执行之后周期性执行的延迟时间]---不关心其它任务执行的耗时,不会累计时间
executorService.scheduleAtFixedRate(runnable_regularly,initDelay,3,TimeUnit.SECONDS);
}
}