在Java开发中,定时任务是一种十分常见的功能.
定时任务是在约定时间内执行的一段程序
如每天凌晨24点备份同步数据,又或者电商平台 30 分钟后自动取消未支付的订单,每隔一个小时拉取一次数据等都需要使用到定时器
- 批量处理数据:批量统计上个月的某个数据。
- 时间驱动的场景:某个时间点发送短信、邮件。
- 固定频率的场景:每隔5分钟需要执行一次
在Java中,实现定时任务的方式有很多,最简单的在线程中通过JDK自带Timer,Thread.sleep睡眠线程,或者采用SpringBoot中的@Schedule注解,或者采用定时线程池ScheduledExecutorService来实现,又或者采用Spring Boot中集成Quartz
框架实现
一、Thread线程等待(最原始最简单方式)
创建一个thread
,然后让它在while
循环里一直运行着,通过sleep
方法来达到定时任务的效果
匿名内部类实现 java.lang.Runnable 接口
/**
* 线程等待;实现java.lang.Runnable接口
*/
public class ThreadTask {
public static void main(String[] args) {
final long timeInterval = 1000;
//创建线程(匿名内部类方式)
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true){
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
String dateStr = sdf.format(new Date());
System.out.println("线程等待实现定时任务:" + dateStr);
try {
Thread.sleep(timeInterval);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
//开启线程
thread.start();
}
}
自定义类实现 java.lang.Runnable 接口
/**
* 线程等待;自定义类实现java.lang.Runnable接口
*/
public class ThreadTask1 {
public static void main(String[] args) {
//自定义类实现java.lang.Runnable接口
MyRunnable runnable = new MyRunnable();
//创建线程(自定义类MyRunnable实现java.lang.Runnable接口)
Thread t = new Thread(runnable);
//开启线程
t.start();
}
}
/**
* 自定义类MyRunnable实现java.lang.Runnable接口
*/
class MyRunnable implements Runnable{
final long timeInterval = 1000;
@Override
public void run() {
while (true){
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
String dateStr = sdf.format(new Date());
System.out.println("线程等待实现定时任务1:" + dateStr);
try {
Thread.sleep(timeInterval);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
二、Timer(最古老方式)
JDK
自带的Timer API
算是最古老的定时任务实现方式了。Timer
是一种定时器工具,使用java.util.Timer
工具类。用来在一个后台线程计划执行指定任务。它可以安排任务“执行一次”或者定期“执行多次”。
/**
* Timer是JAVA自带的定时任务类;优点:使用方便
*/
public class MyTimerTask {
public static void main(String[] args) {
// 定义一个定时任务
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
String dateStr = sdf.format(new Date());
System.out.println("运行定时任务:" + dateStr);
}
};
// 计时器
Timer timer = new Timer();
// 添加执行任务(延迟 1s 执行,每 3s 执行一次)
timer.schedule(timerTask, 1000, 3000);
}
}
Timer类核心方法如下:
// 在指定延迟时间后执行指定的任务
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();
Timer 缺点分析
1、任务执行时间长影响其他任务
当一个任务的执行时间过长时,会影响其他任务的调度
/**
* Timer是JAVA自带的定时任务类;优点:使用方便
*/
public class MyTimerTask1 {
public static void main(String[] args) {
// 定时任务1
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
String dateStr = sdf.format(new Date());
System.out.println("进入定时任务1:" + dateStr);
try {
// 休眠 5 秒
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
dateStr = sdf.format(new Date());
System.out.println("运行定时任务1:" + dateStr);
}
};
// 定时任务2
TimerTask timerTask2 = new TimerTask() {
@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
String dateStr = sdf.format(new Date());
System.out.println("运行定时任务2:" + dateStr);
}
};
// 计时器
Timer timer = new Timer();
// 添加执行任务(延迟 1s 执行,每 2s 执行一次)
timer.schedule(timerTask, 1000, 2000);
timer.schedule(timerTask2, 1000, 2000);
}
}
当任务 1 运行时间超过设定的间隔时间时,任务 2 也会延迟执行。 原本任务 1 和任务 2 的执行时间间隔都是 2s,但因为任务 1 执行了 5s,因此任务 2 的执行时间间隔也变成了 10s(和原定时间不符)
2、任务异常影响其他任务
使用 Timer 类实现定时任务时,当一个任务抛出异常,其他任务也会终止运行
Timer线程是不会捕获异常的,如果TimerTask
抛出的了未检查异常则会导致Timer
线程终止,同时Timer也不会重新恢复线程的执行,它会错误的认为整个Timer
线程都会取消。同时,已经被安排单尚未执行的TimerTask
也不会再执行了,新的任务也不能被调度
/**
* Timer是JAVA自带的定时任务类;优点:使用方便
*/
public class MyTimerTask2 {
public static void main(String[] args) {
// 定时任务1
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
String dateStr = sdf.format(new Date());
System.out.println("进入定时任务1:" + dateStr);
//发生异常
int num = 10 / 0;
dateStr = sdf.format(new Date());
System.out.println("运行定时任务1:" + dateStr);
}
};
// 定时任务2
TimerTask timerTask2 = new TimerTask() {
@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
String dateStr = sdf.format(new Date());
System.out.println("运行定时任务2:" + dateStr);
}
};
// 计时器
Timer timer = new Timer();
// 添加执行任务(延迟 1s 执行,每 2s 执行一次)
timer.schedule(timerTask, 1000, 2000);
timer.schedule(timerTask2, 1000, 2000);
}
}
Timer 小结
Timer 类实现定时任务的优点是方便,因为它是 JDK 自定的定时任务,但缺点是任务如果执行时间太长或者是任务执行异常,会影响其他任务调度
三、ScheduledExecutorService;ScheduledThreadPool
ScheduledExecutorService 是JAVA 1.5后新增的定时任务接口,它是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行(任务是并发执行,互不影响)
ScheduledExecutorService 可以实现Timer具备的所有功能,并解决了 Timer类存在的问题
注:
只有当执行调度任务时,ScheduledExecutorService
才会真正启动一个线程,其余时间ScheduledExecutorService
都是出于轮询任务的状态
/**
* ScheduledExecutorService是JAVA 1.5后新增的定时任务接口,它是基于线程池设计的定时任务类,每个调度任务都会分配到线程池中的一个线程去执行(任务是并发执行,互不影响)
* ScheduledExecutorService可以实现Timer具备的所有功能,并解决了 Timer类存在的问题
*/
public class MyScheduledExecutorService {
public static void main(String[] args) {
// 创建任务队列;10为线程数量
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
//执行任务
scheduledExecutorService.scheduleAtFixedRate(() -> {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
String dateStr = sdf.format(new Date());
System.out.println("ScheduledExecutorService执行定时任务:" + dateStr);
},1,2, TimeUnit.SECONDS);// 1s 后开始执行,每 2s 执行一次
}
}
public class ScheduledThreadPool {
public static void main(String[] args) {
// 参数代表可以同时执行的定时任务个数
ScheduledExecutorService service = Executors.newScheduledThreadPool(3);
/**
* schedule:延时2秒执行一次任务
*/
service.schedule(() -> {
System.out.println("task0-start");
sleep(2);
System.out.println("task0-end");
}, 2, TimeUnit.SECONDS);
/**
* scheduleAtFixedRate:1秒后,每间隔2秒执行一次任务
* 注意,如果任务的执行时间(例如6秒)大于间隔时间,则会等待任务执行结束后直接开始下次任务
*/
service.scheduleAtFixedRate(() -> {
System.out.println("task1-start");
sleep(2);
System.out.println("task1-end");
}, 1, 2, TimeUnit.SECONDS);
/**
* scheduleWithFixedDelay:1秒后,每次延时2秒执行一次任务
* 注意,这里是等待上次任务执行结束后,再延时固定时间后开始下次任务
*/
service.scheduleWithFixedDelay(() -> {
System.out.println("task2-start");
sleep(2);
System.out.println("task2-end");
}, 1, 2, TimeUnit.SECONDS);
}
private static void sleep(long time) {
try {
TimeUnit.SECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
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)。然后传入线程池,启动线程去执行该任务
scheduleAtFixedRate
方法
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
command
为被执行的线程;initialDelay
为初始化后延时执行时间;period
为两次开始执行最小间隔时间;unit
为计时单位
scheduleWithFixedDelay
方法
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
command
为被执行的线程;initialDelay
为初始化后延时执行时间;period
为前一次执行结束到下一次执行开始的间隔时间(间隔执行延迟时间);unit为计时单位
ScheduledExecutorService;ScheduledThreadPool小结
在单机生产环境下建议使用 ScheduledExecutorService 来执行定时任务,它是 JDK 1.5 之后自带的 API,因此使用起来也比较方便,并且使用 ScheduledExecutorService 来执行任务,不会造成任务间的相互影响
四、Spring Task;@Scheduled
从Spring 3
开始,Spring
自带了一套定时任务工具Spring-Task(
基于注解 [@Scheduled,@EnableScheduling
] 形式实现)
,可以把它看成是一个轻量级的Quartz
,使用起来十分简单,除Spring
相关的包外不需要额外的包,支持注解和配置文件两种形式。通常情况下在Spring
体系内,针对简单的定时任务,可直接使用Spring
提供的功能。
如果使用的是 Spring 或 Spring Boot 框架,可以直接使用 Spring Framework 自带的定时任务,使用上述定时任务的实现方式,很难实现设定了具体时间的定时任务,如当我们需要每周五来执行某项任务时,但如果使用 Spring Task 就可轻松的实现
以 Spring Boot 为例,实现定时任务只需两步:
开启定时任务
添加定时任务
1、开启定时任务
如果是在Spring Boot
项目中,需要在启动类上添加@EnableScheduling
来开启定时任务
@EnableScheduling // 开启定时任务
@SpringBootApplication
public class Job4ScheduledApplication {
public static void main(String[] args) {
SpringApplication.run(Job4ScheduledApplication.class, args);
}
}
2、添加定时任务
定时任务的添加只需要使用 @Scheduled
注解标注即可,如果有多个定时任务可以创建多个 @Scheduled
注解标注的方法
@Component //@Component用于实例化类,将其类托管给 Spring 容器
public class TaskJobUtil {
/**
* cron表达式:表示每2秒 执行任务
*/
@Scheduled(cron = "0/2 * * * * ?")
public void task() {
System.out.println("task0-start");
sleep(5);
System.out.println("task0-end");
}
/**
* fixedRate:每间隔2秒执行一次任务
* 注意,默认情况下定时任务是在同一线程同步执行的,如果任务的执行时间(如5秒)大于间隔时间,则会等待任务执行结束后直接开始下次任务
*/
@Scheduled(fixedRate = 2000)
public void task0() {
System.out.println("task0-start");
sleep(5);
System.out.println("task0-end");
}
/**
* fixedDelay:每次延时2秒执行一次任务
* 注意,这里是等待上次任务执行结束后,再延时固定时间后开始下次任务
*/
@Scheduled(fixedDelay = 2000)
public void task1() {
System.out.println("task1-start");
sleep(5);
System.out.println("task1-end");
}
/**
* initialDelay:首次任务启动的延时时间
*/
@Scheduled(initialDelay = 2000, fixedDelay = 3000)
public void task2() {
System.out.println("task2-start");
sleep(5);
System.out.println("task2-end");
}
private void sleep(long time) {
try {
TimeUnit.SECONDS.sleep(time);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
CronTrigger(Cron触发器)功能非常强大,是基于日历的作业调度,而SimpleTrigger是精准指定间隔,所以相比SimpleTrigger,CroTrigger更加常用。CroTrigger是基于Cron表达式的
Cron 表达式是一个字符串,以5或6个空格隔开,分为6或7个域,每一个域代表一个含义
Cron的表达式被用来配置CronTrigger实例
从左到右分别为:秒、分、时、日期、月份、星期几、年份
具体参考博文:
https://blog.csdn.net/MinggeQingchun/article/details/125865778
Cron 在线生成
crontab执行时间计算 - 在线工具
quartz/Cron/Crontab表达式在线生成工具-BeJSON.com
[秒] [分] [时] [日期] [月] [星期] [秒] [分] [时] [日期] [月] [星期] [年]
*:表示任何时间触发任务 ,
:表示指定的时间触发任务
-:表示一段时间内触发任务
/:表示从哪一个时刻开始,每隔多长时间触发一次任务。
?:表示用于月中的天和周中的天两个子表达式,表示不指定值
常用表达式例子
(1)0/2 * * * * ? 表示每2秒 执行任务
(1)0 0/2 * * * ? 表示每2分钟 执行任务
(1)0 0 2 1 * ? 表示在每月的1日的凌晨2点调整任务
(2)0 15 10 ? * MON-FRI 表示周一到周五每天上午10:15执行作业
(3)0 15 10 ? 6L 2002-2006 表示2002-2006年的每个月的最后一个星期五上午10:15执行作
(4)0 0 10,14,16 * * ? 每天上午10点,下午2点,4点
(5)0 0/30 9-17 * * ? 朝九晚五工作时间内每半小时
(6)0 0 12 ? * WED 表示每个星期三中午12点
(7)0 0 12 * * ? 每天中午12点触发
(8)0 15 10 ? * * 每天上午10:15触发
(9)0 15 10 * * ? 每天上午10:15触发
(10)0 15 10 * * ? 每天上午10:15触发
(11)0 15 10 * * ? 2005 2005年的每天上午10:15触发
(12)0 * 14 * * ? 在每天下午2点到下午2:59期间的每1分钟触发
(13)0 0/5 14 * * ? 在每天下午2点到下午2:55期间的每5分钟触发
(14)0 0/5 14,18 * * ? 在每天下午2点到2:55期间和下午6点到6:55期间的每5分钟触发
(15)0 0-5 14 * * ? 在每天下午2点到下午2:05期间的每1分钟触发
(16)0 10,44 14 ? 3 WED 每年三月的星期三的下午2:10和2:44触发
(17)0 15 10 ? * MON-FRI 周一至周五的上午10:15触发
(18)0 15 10 15 * ? 每月15日上午10:15触发
(19)0 15 10 L * ? 每月最后一日的上午10:15触发
(20)0 15 10 ? * 6L 每月的最后一个星期五上午10:15触发
(21)0 15 10 ? * 6L 2002-2005 2002年至2005年的每月的最后一个星期五上午10:15触发
(22)0 15 10 ? * 6#3 每月的第三个星期五上午10:15触发
fixedDelay和fixedRate的区别
fixedRate
有一个时刻表的概念,在任务启动时,T1、T2、T3就已经排好了执行的时刻,比如1分、2分、3分,当T1的执行时间大于1分钟时,就会造成T2晚点,当T1执行完时T2立即执行。
fixedDelay
比较简单,表示上个任务结束,到下个任务开始的时间间隔。无论任务执行花费多少时间,两个任务间的间隔始终是一致的
注:
Spring Task
本身不支持持久化,也没有推出官方的分布式集群模式,只能靠开发者在业务应用中自己手动扩展实现,无法满足可视化,易配置的需求
基于Spring Task
实现定时任务
-
优点:
- 不需要依赖外部框架。
- 简单快速实现任务。
@EnableScheduling
、@Scheduled
注解
-
缺点:
- 无法管理任务。要停止某个任务,必须重新发布。
- 不支持动态调整。修改任务参数需要重启项目。
- 不支持集群方式部署。集群模式下会出现任务多次被调度执行的情况,因为集群的节点之间是不会共享任务信息的,每个节点上的任务都会按时执行
五、Quartz
除了JDK自带的API之外,我们还可以使用开源的框架来实现,比如Quartz
Quartz是Job scheduling
(作业调度)领域的一个开源项目,Quartz既可以单独使用也可以跟spring框架整合使用,在实际开发中一般会使用后者。使用Quartz
可以开发一个或者多个定时任务,每个定时任务可以单独指定执行的时间,例如每隔1小时执行一次、每个月第一天上午10点执行一次、每个月最后一天下午5点执行一次等。
Quartz架构图如下:
Quartz通常有三部分组成:调度器(Scheduler
)、任务(JobDetail
)、触发器(Trigger
,包括SimpleTrigger
和CronTrigger
)
1、Job
:
定义具体要执行的任务
2、JobDetail
:
配置要执行任务的描述信息,即如何去定位要执行的Job
,每次执行任务时,都会根据JobDetail
创建一个Job
对象,避免任务并发执行时访问同一个Job
对象产生问题
jobdetail 就是对job的定义,而job是具体执行的逻辑内容。 具体的执行的逻辑需要实现 job类,并实现execute方法。如果使用jobdetail来定义,那么每次调度都会创建一个new job实例,这样带来的好处就是任务并发执行的时候,互不干扰,不会对临界资源造成影响
3、Trigger
:
触发器,配置任务执行的时间规则,需要和一个JobDetail
关联起来
在 Quartz 中,trigger 是用于定义 Job 何时执行。当用 Scheduler 注册一个 Job 的时候要创建一个 Trigger 与这个 Job 相关联。
Quartz 中主要提供了四种类型的 Trigger:包括SimpleTrigger、CronTirgger//DateIntervalTrigger和 NthIncludedDayTrigger。这四种 trigger 可以满足企业应用中的绝大部分需求。
最常用的是 SimpleTrigger 和 CronTrigger
一般来说,如果你需要在一个固定的时间和重复次数或者一个固定的间隔时间,那么SimpleTrigger 比较合适; 如果你有许多复杂的作业调度,那么 CronTrigger 比较合适。
CronTrigger 和 Unix 的 cron 机制基本一样,基于通用的公历,我们需要的只是熟悉cron 表达式的用法。
关于Quartz中时间表达式的设置—–corn表达式:
withIdentity() 给触发器一些属性 比如名字,组名
startNow() 立刻启动
withSchedule(ScheduleBuilder schedBuilder) 以某种触发器触发
usingJobData(String dataKey, Boolean value) 给具体job传递参数
4、Scheduler
:
调度器,它维护了一个JobDetail
和Trigger
的注册表,当任务关联的触发器到达预定的时间,调度器会去执行任务
Scheduler代表一个Quartz的独立运行容器,Trigger和JobDetail可以注册到Scheduler中,两者在Scheduler中拥有各自的组及名称,组及名称是Scheduler查找定位容器中某一对象的依据,Trigger的组及名称必须唯一,JobDetail的组和名称也必须唯一(但可以和Trigger的组和名称相同,因为它们是不同类型的)。
Scheduler定义了多个接口方法,允许外部通过组及名称访问和控制容器中Trigger和JobDetail。 Scheduler可以将Trigger绑定到某一JobDetail中,这样当Trigger触发时,对应的Job就被执行,例如:如schedulerTest.scheduleJob(jobTest, triggerTest)。一个Job可以对应多个Trigger,但一个Trigger只能对应一个Job。
Scheduler拥有一个SchedulerContext,它类似于ServletContext,保存着Scheduler上下文信息,Job和Trigger都可以访问SchedulerContext内的信息。SchedulerContext内部通过一个Map,以键值对的方式维护这些上下文数据,SchedulerContext为保存和获取数据提供了多个put()和getXxx()的方法。可以通过Scheduler# getContext()获取对应的SchedulerContext实例。
scheduler 由 scheduler 工厂创建:包括DirectSchedulerFactory 和 StdSchedulerFactory(STD:standard标准的意思)。 第二种工厂 StdSchedulerFactory 使用较多,因为 DirectSchedulerFactory 使用起来不够方便,需要作许多详细的手工编码设置。
scheduler 主要有三种:RemoteMBeanScheduler, RemoteScheduler 和 StdScheduler。
scheduler 除了启动外,scheduler 操作包括查询、设置 scheduler 为 standby 模式、继续、停止。 启动scheduler 非常简单,只需要调用 start() 方法即可。只有在scheduler 有实例或standby 模式才能调用start() 方法,一旦调用shutdown() 方法之后就不能再调用start() 方法。
(1)在Spring Boot中集成Quartz
需要先添加如下Maven依赖
<!-- Spring Boot中集成Quartz -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
<version>2.6.6</version>
</dependency>
(2)在启动类添加@EnableScheduling
注解来开启定时任务
(3)配置文件quartz.properties
#主要分为scheduler、threadPool、jobStore、dataSource等部分
org.quartz.scheduler.instanceId=AUTO
org.quartz.scheduler.instanceName=DefaultQuartzScheduler
#如果您希望Quartz Scheduler通过RMI作为服务器导出本身,则将“rmi.export”标志设置为true
#在同一个配置文件中为'org.quartz.scheduler.rmi.export'和'org.quartz.scheduler.rmi.proxy'指定一个'true'值是没有意义的,如果你这样做'export'选项将被忽略
org.quartz.scheduler.rmi.export=false
#如果要连接(使用)远程服务的调度程序,则将“org.quartz.scheduler.rmi.proxy”标志设置为true。您还必须指定RMI注册表进程的主机和端口 - 通常是“localhost”端口1099
org.quartz.scheduler.rmi.proxy=false
org.quartz.scheduler.wrapJobExecutionInUserTransaction=false
#实例化ThreadPool时,使用的线程类为SimpleThreadPool
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
#threadCount和threadPriority将以setter的形式注入ThreadPool实例
#并发个数 如果你只有几个工作每天触发几次 那么1个线程就可以,如果你有成千上万的工作,每分钟都有很多工作 那么久需要50-100之间.
#只有1到100之间的数字是非常实用的
org.quartz.threadPool.threadCount=5
#优先级 默认值为5
org.quartz.threadPool.threadPriority=5
#可以是“true”或“false”,默认为false
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
#在被认为“misfired”(失火)之前,调度程序将“tolerate(容忍)”一个Triggers(触发器)将其下一个启动时间通过的毫秒数。默认值(如果您在配置中未输入此属性)为60000(60秒)
org.quartz.jobStore.misfireThreshold=5000
# 默认存储在内存中,RAMJobStore快速轻便,但是当进程终止时,所有调度信息都会丢失
#org.quartz.jobStore.class=org.quartz.simpl.RAMJobStore
#持久化方式,默认存储在内存中,此处使用数据库方式
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
#您需要为JobStore选择一个DriverDelegate才能使用。DriverDelegate负责执行特定数据库可能需要的任何JDBC工作
# StdJDBCDelegate是一个使用“vanilla”JDBC代码(和SQL语句)来执行其工作的委托,用于完全符合JDBC的驱动程序
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
#可以将“org.quartz.jobStore.useProperties”配置参数设置为“true”(默认为false),以指示JDBCJobStore将JobDataMaps中的所有值都作为字符串,
#因此可以作为名称 - 值对存储而不是在BLOB列中以其序列化形式存储更多复杂的对象。从长远来看,这是更安全的,因为您避免了将非String类序列化为BLOB的类版本问题
org.quartz.jobStore.useProperties=true
#表前缀
org.quartz.jobStore.tablePrefix=QRTZ_
#数据源别名,自定义
org.quartz.jobStore.dataSource=qzDS
#使用阿里的druid作为数据库连接池
org.quartz.dataSource.qzDS.connectionProvider.class=org.example.config.DruidPoolingconnectionProvider
org.quartz.dataSource.qzDS.URL=jdbc:mysql://127.0.0.1:3306/test_quartz?characterEncoding=utf8&useSSL=false&autoReconnect=true&serverTimezone=UTC
org.quartz.dataSource.qzDS.user=root
org.quartz.dataSource.qzDS.password=123456
org.quartz.dataSource.qzDS.driver=com.mysql.jdbc.Driver
org.quartz.dataSource.qzDS.maxConnections=10
#设置为“true”以打开群集功能。如果您有多个Quartz实例使用同一组数据库表,则此属性必须设置为“true”,否则您将遇到破坏
#org.quartz.jobStore.isClustered=false
或者添加定时任务配置类
/**
* 定时任务配置
*
* @author hu.tj
*/
@Configuration
public class ScheduleConfig
{
@Bean
public SchedulerFactoryBean schedulerFactoryBean(DataSource dataSource)
{
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setDataSource(dataSource);
// quartz参数
Properties prop = new Properties();
prop.put("org.quartz.scheduler.instanceName", "ZM Schedule");
prop.put("org.quartz.scheduler.instanceId", "AUTO");
// 线程池配置
prop.put("org.quartz.threadPool.class", "org.quartz.simpl.SimpleThreadPool");
prop.put("org.quartz.threadPool.threadCount", "20");
prop.put("org.quartz.threadPool.threadPriority", "5");
// JobStore配置
prop.put("org.quartz.jobStore.class", "org.quartz.impl.jdbcjobstore.JobStoreTX");
// 集群配置
prop.put("org.quartz.jobStore.isClustered", "false");
prop.put("org.quartz.jobStore.clusterCheckinInterval", "15000");
prop.put("org.quartz.jobStore.maxMisfiresToHandleAtATime", "1");
prop.put("org.quartz.jobStore.txIsolationLevelSerializable", "true");
// sqlserver 启用
// prop.put("org.quartz.jobStore.selectWithLockSQL", "SELECT * FROM {0}LOCKS UPDLOCK WHERE LOCK_NAME = ?");
prop.put("org.quartz.jobStore.misfireThreshold", "12000");
prop.put("org.quartz.jobStore.tablePrefix", "QRTZ_");
factory.setQuartzProperties(prop);
factory.setSchedulerName("ZMScheduler");
// 延时启动
factory.setStartupDelay(1);
factory.setApplicationContextSchedulerContextKey("applicationContextKey");
// 可选,QuartzScheduler
// 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了
factory.setOverwriteExistingJobs(true);
// 设置自动启动,默认为true
factory.setAutoStartup(true);
return factory;
}
}
关于配置详细解释:任务调度框架Quartz(五)Quartz任务调度框架之最全Quartz系统参数配置详解_青山师的博客-CSDN博客
也可查看官网:
Quartz Documentation
1、定义Job
定义Job有两种方式,
第一种是直接定义任务类,并注册到Spring IoC容器中:
@Service
public class QuartzJobService {
public void taskJob(){
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
String dateStr = sdf.format(new Date());
System.out.println("job0-start:" + dateStr);
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("job0-end:" + dateStr);
}
}
第二种是继承QuartzJobBean
,重写executeInternal
方法,这种方式可以接受JobDetail传递的参数
:
public class QuartzJobDetail extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
String dateStr = sdf.format(new Date());
System.out.println("job1-start:" + dateStr);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 获取参数
JobDataMap jobDataMap = jobExecutionContext.getMergedJobDataMap();
String date = jobDataMap.getString("date");
System.out.println("参数:" + date);
System.out.println("job1-end:" + dateStr);
}
}
这样就把JobDetail
和我们之前定义的QuartzJob
关联起来了。
2、配置JobDetail
JobDetail
可以使用MethodInvokingJobDetailFactoryBean
或者JobDetailFactoryBean
配置,配置工作需要在一个Spring配置类中完成,可以定义一个QuartzConfig
配置类,首先看MethodInvokingJobDetailFactoryBean
的使用:
@Configuration
public class QuartzConfig {
/**
* 配置JobDetail
*/
//JobDetail可以使用MethodInvokingJobDetailFactoryBean配置
@Bean
public MethodInvokingJobDetailFactoryBean methodInvokingJobDetailFactoryBean(){
MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean();
// 指定任务类在IoC容器中的Bean名称
bean.setTargetBeanName("quartzJobService");
// 指定要执行的方法名称
bean.setTargetMethod("taskJob");
return bean;
}
}
这样就把JobDetail
和之前QuartzJob
所定义的任务关联起来了,接下来看JobDetailFactoryBean
:
@Configuration
public class QuartzCronTriggerConfig {
//JobDetail可以使用JobDetailFactoryBean配置
@Bean
public JobDetailFactoryBean jobDetailFactoryBean() {
JobDetailFactoryBean bean = new JobDetailFactoryBean();
// 指定任务类名称
bean.setJobClass(QuartzJobDetail.class);
// 准备参数
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("date", "2020-08-08");
// 传递参数
bean.setJobDataMap(jobDataMap);
return bean;
}
}
这样就把JobDetail
和之前定义的QuartzJob2
关联起来了,同时传递了参数。
3、配置Trigger
Trigger
同样定义在QuartzConfig
配置类里,常用的Trigger
有SimpleTrigger
、CronTrigger
等,它们分别可以通过SimpleTriggerFactoryBean
、CronTriggerFactoryBean
来完成配置,我们先用SimpleTriggerFactoryBean
配置的触发器关联MethodInvokingJobDetailFactoryBean
配置的JobDetail
:
@Configuration
public class QuartzConfig {
@Bean
public SimpleTriggerFactoryBean simpleTriggerFactoryBean() {
SimpleTriggerFactoryBean bean = new SimpleTriggerFactoryBean();
bean.setRepeatCount(10);
bean.setRepeatInterval(2000);
// 关联JobDetail
bean.setJobDetail(methodInvokingJobDetailFactoryBean().getObject());
return bean;
}
}
SimpleTriggerFactoryBean
的用法比较简单
再用CronTriggerFactoryBean
配置的触发器关联JobDetailFactoryBean
配置的JobDetail
:
@Configuration
public class QuartzConfig {
@Bean
public CronTriggerFactoryBean cronTriggerFactoryBean() {
CronTriggerFactoryBean bean = new CronTriggerFactoryBean();
bean.setCronExpression("0/2 * * * * ? 2020");
// 关联JobDetail
bean.setJobDetail(jobDetailFactoryBean().getObject());
return bean;
}
}
CronTriggerFactoryBean
可以实现类似Spring中@Scheduled
的cron
表达式的功能,同时支持了年份的配置。
4、配置Scheduler
最后一步就是通过SchedulerFactoryBean
来配置Scheduler
,来注册Trigger
@Configuration
public class QuartzConfig {
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean bean = new SchedulerFactoryBean();
// 注册两个Trigger
bean.setTriggers(simpleTriggerFactoryBean().getObject(), cronTriggerFactoryBean().getObject());
return bean;
}
}
Quartz
会使用异步线程去执行定时任务,不会出现像@Scheduled
中定时任务在同一线程同步执行的情况。
或者编写了Configuration 配置类
/**
* 第一种:Simple类型
* 将该类标记为配置文件
* 创建 JobDetail
* 创建 SimpleTrigger
*/
@Configuration
public class QuartzSimpleTriggerConfig {
/**
* 配置JobDetail
*/
//JobDetail可以使用MethodInvokingJobDetailFactoryBean配置
@Bean
public MethodInvokingJobDetailFactoryBean methodInvokingJobDetailFactoryBean(){
MethodInvokingJobDetailFactoryBean bean = new MethodInvokingJobDetailFactoryBean();
// 指定任务类在IoC容器中的Bean名称
bean.setTargetBeanName("quartzJobService");
// 指定要执行的方法名称
bean.setTargetMethod("taskJob");
return bean;
}
//通过SimpleTriggerFactoryBean配置的触发器关联MethodInvokingJobDetailFactoryBean配置的JobDetail
@Bean
public SimpleTriggerFactoryBean simpleTriggerFactoryBean() {
SimpleTriggerFactoryBean bean = new SimpleTriggerFactoryBean();
bean.setRepeatCount(10);
bean.setRepeatInterval(2000);
// 关联JobDetail
bean.setJobDetail(methodInvokingJobDetailFactoryBean().getObject());
return bean;
}
/**
* 配置Scheduler
* */
//通过SchedulerFactoryBean来配置Scheduler,来注册Trigger
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean bean = new SchedulerFactoryBean();
// 注册两个Trigger
bean.setTriggers(simpleTriggerFactoryBean().getObject());
return bean;
}
}
/**
* 第二种:Cron类型
* 将该类标记为配置文件
* 创建 JobDetail
* 创建 CronTrigger
*/
@Configuration
public class QuartzCronTriggerConfig {
//JobDetail可以使用JobDetailFactoryBean配置
@Bean
public JobDetailFactoryBean jobDetailFactoryBean() {
JobDetailFactoryBean bean = new JobDetailFactoryBean();
// 指定任务类名称
bean.setJobClass(QuartzJobDetail.class);
// 准备参数
JobDataMap jobDataMap = new JobDataMap();
jobDataMap.put("date", "2020-08-08");
// 传递参数
bean.setJobDataMap(jobDataMap);
return bean;
}
/**
* 配置Trigger
*/
//通过CronTriggerFactoryBean配置的触发器关联MethodInvokingJobDetailFactoryBean配置的JobDetail
@Bean
public CronTriggerFactoryBean cronTriggerFactoryBean() {
CronTriggerFactoryBean bean = new CronTriggerFactoryBean();
bean.setCronExpression("0/3 * * * * ? 2022");
// 关联JobDetail
bean.setJobDetail(jobDetailFactoryBean().getObject());
return bean;
}
/**
* 配置Scheduler
* */
//通过SchedulerFactoryBean来配置Scheduler,来注册Trigger
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean bean = new SchedulerFactoryBean();
// 注册两个Trigger
bean.setTriggers(cronTriggerFactoryBean().getObject());
return bean;
}
}
非SpringBoot版
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>2.3.2</version>
</dependency>
public class TaskJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println(new Date() + " : 任务「TaskJob 」被执行");
}
}
public class MyScheduler {
public static void main(String[] args) throws SchedulerException {
// 1、创建调度器Scheduler
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
// 2、创建JobDetail实例,并与PrintJob类绑定(Job执行内容)
JobDetail jobDetail = JobBuilder.newJob(PrintJob.class)
.withIdentity("job", "group").build();
// 3、构建Trigger实例,每隔1s执行一次
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger", "triggerGroup")
.startNow()//立即生效
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(1)//每隔1s执行一次
.repeatForever()).build();//一直执行
//4、Scheduler绑定Job和Trigger,并执行
scheduler.scheduleJob(jobDetail, trigger);
System.out.println("--------scheduler start ! ------------");
scheduler.start();
}
}
六、Xxl-Job分布式任务调度平台
分布式任务调度平台XXL-JOB
XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用
xxl是xxl-job的开发者大众点评的【许雪里】名称的拼音开头
https://blog.csdn.net/MinggeQingchun/article/details/129883009
可参考
https://www.cnblogs.com/oeong/p/16212448.html
Java 定时任务-最简单的3种实现方法_深海呐的博客-CSDN博客_java定时任务
java定时任务_定时任务3种实现方式_IT枫斗者的博客-CSDN博客_java定时任务文章来源:https://www.toymoban.com/news/detail-786107.html
https://www.jb51.net/article/226802.htm#_label4文章来源地址https://www.toymoban.com/news/detail-786107.html
到了这里,关于Java -- 定时任务实现方式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!