Java中定时任务的6种实现⽅式
⽬录
1、线程等待实现
2、JDK⾃带Timer实现
2.1 核⼼⽅法
2.2使⽤⽰例
2.2.1指定延迟执⾏⼀次
2.2.2固定间隔执⾏
2.2.3固定速率执⾏
2.3 schedule与scheduleAtFixedRate区别
2.3.1schedule侧重保持间隔时间的稳定
2.3.2scheduleAtFixedRate保持执⾏频率的稳定
2.4 Timer的缺陷
3、JDK⾃带ScheduledExecutorService
3.1 scheduleAtFixedRate⽅法
3.2 scheduleWithFixedDelay⽅法
4、Quartz框架实现
4.1 Quartz集成
5、Spring Task
5.1 fixedDelay和fixedRate的区别
5.2 Spring Task的缺点
6、分布式任务调度
6.1 Quartz分布式
6.2 轻量级神器XXL-Job
6.3 其他框架
7、⼩结
前⾔:
⼏乎在所有的项⽬中,定时任务的使⽤都是不可或缺的,如果使⽤不当甚⾄会造成资损。还记得多年前在做⾦融系统时,出款业务是通过定时任务对外打款,当时由于银⾏接⼝处理能⼒有限,外加定时任务使⽤不当,导致发出⼤量重复出款请求。还好在后⾯环节将交易卡在了系统内部,未发⽣资损。
所以,系统的学习⼀下定时任务,是⾮常有必要的。这篇⽂章就带⼤家整体梳理学习⼀下Java领域中常见的⼏种
定时任务实现。
1、线程等待实现
先从最原始最简单的⽅式来讲解。可以先创建⼀个thread,然后让它在while循环⾥⼀直运⾏着,通过sleep⽅法来达到定时任务的效果。
public class Task {
public static void main(String[] args) {
// run in a second
final long timeInterval = 1000;
Runnable runnable = new Runnable() {
@Override
public void run() {
while (true) {
System.out.println("Hello !!");
try {
Thread.sleep(timeInterval);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
}
这种⽅式简单直接,但是能够实现的功能有限,⽽且需要⾃⼰来实现。
2、JDK⾃带Timer实现
⽬前来看,JDK⾃带的Timer API算是最古⽼的定时任务实现⽅式了。Timer是⼀种定时器⼯具,⽤来在⼀个后台线程计划执⾏指定任务。它可以安排任务“执⾏⼀次”或者定期“执⾏多次”。
java做什么的在实际的开发当中,经常需要⼀些周期性的操作,⽐如每5分钟执⾏某⼀操作等。对于这样的操作最⽅便、⾼效的实现⽅式就是使⽤java.util.Timer⼯具类。
2.1 核⼼⽅法
imer类的核⼼⽅法如下:
// 在指定延迟时间后执⾏指定的任务
schedule(TimerTask task,long delay);
// 在指定时间执⾏指定的任务。(只执⾏⼀次)
schedule(TimerTask task, Date time);
// 延迟指定时间(delay)之后,开始以指定的间隔(period)重复执⾏指定的任务
schedule(TimerTask task,long delay,long period);
// 在指定的时间开始按照指定的间隔(period)重复执⾏指定的任务
schedule(TimerTask task, Date firstTime , long period);
// 在指定的时间开始进⾏重复的固定速率执⾏任务
scheduleAtFixedRate(TimerTask task,Date firstTime,long period);
// 在指定的延迟后开始进⾏重复的固定速率执⾏任务
scheduleAtFixedRate(TimerTask task,long delay,long period);
// 终⽌此计时器,丢弃所有当前已安排的任务。
cancal();
// 从此计时器的任务队列中移除所有已取消的任务。
purge();
2.2使⽤⽰例
下⾯⽤⼏个⽰例演⽰⼀下核⼼⽅法的使⽤。⾸先定义⼀个通⽤的TimerTask类,⽤于定义⽤执⾏的任务。
public class DoSomethingTimerTask extends TimerTask {
private String taskName;
public DoSomethingTimerTask(String taskName) {
this.taskName = taskName;
}
@Override
public void run() {
System.out.println(new Date() + " : 任务「" + taskName + "」被执⾏。");
}
}
2.2.1指定延迟执⾏⼀次
在指定延迟时间后执⾏⼀次,这类是⽐较常见的场景,
⽐如:当系统初始化某个组件之后,延迟⼏秒中,然后进⾏定时任务的执⾏。
public class DelayOneDemo {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new DoSomethingTimerTask("DelayOneDemo"),1000L);
}
}
执⾏上述代码,延迟⼀秒之后执⾏定时任务,并打印结果。其中第⼆个参数单位为毫秒。
2.2.2固定间隔执⾏
在指定的延迟时间开始执⾏定时任务,定时任务按照固定的间隔进⾏执⾏。⽐如:延迟2秒执⾏,固定执⾏间隔为1秒。public class PeriodDemo {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new DoSomethingTimerTask("PeriodDemo"),2000L,1000L);
}
}
执⾏程序,会发现2秒之后开始每隔1秒执⾏⼀次。
2.2.3固定速率执⾏
在指定的延迟时间开始执⾏定时任务,定时任务按照固定的速率进⾏执⾏。
⽐如:延迟2秒执⾏,固定速率为1秒。
public class FixedRateDemo {
public static void main(String[] args) {
Timer timer = new Timer();
timer.scheduleAtFixedRate(new DoSomethingTimerTask("FixedRateDemo"),2000L,1000L);
}
}
执⾏程序,会发现2秒之后开始每隔1秒执⾏⼀次。
此时,你是否疑惑schedule与scheduleAtFixedRate效果⼀样,为什么提供两个⽅法,它们有什么区别?
2.3 schedule与scheduleAtFixedRate区别
在了解schedule与scheduleAtFixedRate⽅法的区别之前,先看看它们的相同点:
任务执⾏未超时,下次执⾏时间 = 上次执⾏开始时间 + period;
任务执⾏超时,下次执⾏时间 = 上次执⾏结束时间;
在任务执⾏未超时时,它们都是上次执⾏时间加上间隔时间,来执⾏下⼀次任务。⽽执⾏超时时,都是⽴马执⾏。
它们的不同点在于侧重点不同,schedule⽅法侧重保持间隔时间的稳定,⽽scheduleAtFixedRate⽅法更加侧重于保持执⾏频率的稳定。
2.3.1schedule侧重保持间隔时间的稳定
schedule⽅法会因为前⼀个任务的延迟⽽导致其后⾯的定时任务延时。计算公式为scheduledExecutionTime(第n+1次) = realExecutionTime(第n次) + periodTime。
也就是说如果第n次执⾏task时,由于某种原因这次执⾏时间过长,执⾏完后的systemCurrentTime>= scheduledExecutionTime(第n+1次),则此时不做时隔等待,⽴即执⾏第n+1次task。
⽽接下来的第n+2次task的scheduledExecutionTime(第n+2次)就随着变成了realExecutionTime(第n+1次)+periodTime。这个⽅法更注重保持间隔时间的稳定。
2.3.2scheduleAtFixedRate保持执⾏频率的稳定
scheduleAtFixedRate在反复执⾏⼀个task的计划时,每⼀次执⾏这个task的计划执⾏时间在最初就被定下来了,也就
是scheduledExecutionTime(第n次)=firstExecuteTime +n*periodTime。
如果第n次执⾏task时,由于某种原因这次执⾏时间过长,执⾏完后的systemCurrentTime>= scheduledExecutionTime(第n+1次),则此时不做period间隔等待,⽴即执⾏第n+1次task。
接下来的第n+2次的task的scheduledExecutionTime(第n+2次)依然还是firstExecuteTime+(n+2)*periodTime这在第⼀次执⾏task就定下
来了。说⽩了,这个⽅法更注重保持执⾏频率的稳定。
如果⽤⼀句话来描述任务执⾏超时之后schedule和scheduleAtFixedRate的区别就是:schedule的策略是错过了就错过了,后续按照新的节奏来⾛;scheduleAtFixedRate的策略是如果错过了,就努⼒追上原来的节奏(制定好的节奏)。
2.4 Timer的缺陷
Timer计时器可以定时(指定时间执⾏任务)、延迟(延迟5秒执⾏任务)、周期性地执⾏任务(每隔个1秒执⾏任务)。但是,Timer存在⼀些缺陷。⾸先Timer对调度的⽀持是基于绝对时间的,⽽不是相对时间,所以它对系统时间的改变⾮常敏感。
其次Timer线程是不会捕获异常的,如果TimerTask抛出的了未检查异常则会导致Timer线程终⽌,同时Timer也不会重新恢复线程的执⾏,它会错误的认为整个Timer线程都会取消。同时,已经被安排单尚未执⾏的TimerTask也不会再执⾏了,新的任务也不能被调度。故如果TimerTask抛出未检查的异常,Timer将会产⽣⽆法预料的⾏为。
3、JDK⾃带ScheduledExecutorService
ScheduledExecutorService是JAVA 1.5后新增的定时任务接⼝,它是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的⼀个线程去执⾏。也就是说,任务是并发执⾏,互不影响。
需要注意:只有当执⾏调度任务时,ScheduledExecutorService才会真正启动⼀个线程,其余时间ScheduledExecutorService都是出于轮询任务的状态。
ScheduledExecutorService主要有以下4个⽅法:
ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit);
<V> ScheduledFuture<V> schedule(Callable<V> callable,long delay, TimeUnit unit);
ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnitunit);
ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,long initialDelay,long delay,TimeUnitunit);
其中scheduleAtFixedRate和scheduleWithFixedDelay在实现定时程序时⽐较⽅便,运⽤的也⽐较多。
ScheduledExecutorService中定义的这四个接⼝⽅法和Timer中对应的⽅法⼏乎⼀样,只不过Timer的scheduled⽅法需要在外部传⼊⼀个TimerTask的抽象任务。⽽ScheduledExecutorService封装的更加细致了,传Runnable或Callable内部都会做⼀层封装,封装⼀个类似TimerTask的抽象任务类(ScheduledFutureTask)。然后传⼊线程池,启动线程去执⾏该任务。
3.1 scheduleAtFixedRate⽅法
scheduleAtFixedRate⽅法,按指定频率周期执⾏某个任务。定义及参数说明:
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
参数对应含义:command为被执⾏的线程;initialDelay为初始化后延时执⾏时间;period为两次开始执⾏最⼩间隔时间;unit为计时单位。
使⽤实例:
public class ScheduleAtFixedRateDemo implements Runnable{
public static void main(String[] args) {
ScheduledExecutorService executor = wScheduledThreadPool(1);
executor.scheduleAtFixedRate(
new ScheduleAtFixedRateDemo(),
0,
1000,
TimeUnit.MILLISECONDS);
}
@Override
public void run() {
System.out.println(new Date() + " : 任务「ScheduleAtFixedRateDemo」被执⾏。");
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上⾯是scheduleAtFixedRate⽅法的基本使⽤⽅式,但当执⾏程序时会发现它并不是间隔1秒执⾏的,⽽是间隔2秒执⾏。
这是因为,scheduleAtFixedRate是以period为间隔来执⾏任务的,如果任务执⾏时间⼩于period,则上次任务执⾏完成后会间隔period后再去执⾏下⼀次任务;但如果任务执⾏时间⼤于period,则上次任务执⾏完毕后会不间隔的⽴即开始下次任务。3.2 scheduleWithFixedDelay⽅法
scheduleWithFixedDelay⽅法,按指定频率间隔执⾏某个任务。定义及参数说明:
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
参数对应含义:command为被执⾏的线程;initialDelay为初始化后延时执⾏时间;period为前⼀次执⾏结束到下⼀次执⾏开始的间隔时间(间隔执⾏延迟时间);unit为计时单位。
使⽤实例:
public class ScheduleAtFixedRateDemo implements Runnable{
public static void main(String[] args) {
ScheduledExecutorService executor = wScheduledThreadPool(1);
executor.scheduleWithFixedDelay(
new ScheduleAtFixedRateDemo(),
0,
1000,
TimeUnit.MILLISECONDS);
}
@Override
public void run() {
System.out.println(new Date() + " : 任务「ScheduleAtFixedRateDemo」被执⾏。");
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
上⾯是scheduleWithFixedDelay⽅法的基本使⽤⽅式,但当执⾏程序时会发现它并不是间隔1秒执⾏的,⽽是间隔3秒。
这是因为scheduleWithFixedDelay是不管任务执⾏多久,都会等上⼀次任务执⾏完毕后再延迟delay后去执⾏下次任务。
4、Quartz框架实现
除了JDK⾃带的API之外,我们还可以使⽤开源的框架来实现,⽐如Quartz。
Quartz是Job scheduling(作业调度)领域的⼀个开源项⽬,Quartz既可以单独使⽤也可以跟spring框架整合使⽤,在实际开发中⼀般会使⽤后者。使⽤Quartz可以开发⼀个或者多个定时任务,每个定时任务可以单独指定执⾏的时间,例如每隔1⼩时执⾏⼀次、每个⽉第⼀天上午10点执⾏⼀次、每个⽉最后⼀天下午5点执⾏⼀次等。
Quartz通常有三部分组成:调度器(Scheduler)、任务(JobDetail)、触发器(Trigger,包括SimpleTrigger和CronTrigger)。下⾯以具体的实例进⾏说明。
4.1 Quartz集成
要使⽤Quartz,⾸先需要在项⽬的pom⽂件中引⼊相应的依赖:
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。