几种常用定时任务的使用
一、实现定时任务的方案
1 、使用Thread.sleep(1000)
2 、使用jdk的Timer和TimerTask,可以实现简单的间隔执行任务(在指定时间点执行某一任务,也能定时的周期性执行),无法实现按日历去调度执行任务。
3、使用DelayQueue队列
4、使用Quartz,它是一个异步任务调度框架,功能丰富,可以实现按日历调度。
5、使用Spring Task,Spring 3.0后提供Spring Task实现任务调度,支持按日历调度,相比Quartz功能稍简单,但是在开发基本够用,支持注解编程方式。
7、分布式定时任务之XXL-JOB
二、Thread方式
public static void main(String[] args) {
final int[] count = {1};
Runnable runnable = new Runnable() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第 " + count[0]++ + " 次执行...");
}
}
};
Thread thread = new Thread(runnable);
thread.start();
}
三、Timer和TimerTask实现
Timer与TimerTask
public static void main(String[] args) {
final long[] count = {0};
// TimerTask timerTask1=new TimerTask() {
// @Override
// public void run() {
// count[0]++;
// System.out.println("第 " + count[0] + " 次执行...");
// }
// };
TimerTask timerTask2 = new TimerTask() {
@Override
public void run() {
count[0]++;
System.out.println("第 " + count[0] + " 次执行...");
}
};
Timer timer = new Timer();
// 天
long delay = 0;
// 秒
long period = 1000;
timer.scheduleAtFixedRate(timerTask2, delay, period);
}
schedule(2 args) : 指定的计划执行时间 <= 当前时间,则task会被立即执行。
schedule(3 args) : 指定的计划执行时间 <= 当前时间,则task会被立即执行,之后按period参数固定重复执行。
scheduleAtFixedRate(3 args):指定的计划执行时间 <= 当前时间,则task会首先按执行一次;然后按照执行时间、系统当前时间和period参数计算出过期该执行的次数,计算按照: (当前时间 – 计划执行时间)/period,再次执行计算出的次数;最后按period参数固定重复执行。
schedule()方法更注重保持间隔时间的稳定。 scheduleAtFixedRate()方法更注重保持执行频率的稳定。
ScheduledExecutorService
阿里编码规约:
Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行。Timer 优化方案,使用ScheduledExecutorService
ScheduledExecutorService是并发工具包中的类,是最理想的定时任务实现方式。
public static void main(String[] args) {
Runnable runnable1 = new Runnable() {
@Override
public void run() {
System.out.println("Hello runnable1");
}
};
Runnable runnable2 = new Runnable() {
@Override
public void run() {
System.out.println("Hello runnable2");
}
};
//方式一
//定义4个线程
ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
/**
* 参数1:实现Runnable接口的类
* 参数2:启动等待时间,首次执行的延时时间
* 参数3:一个数值,定时执行的间隔时间
* 参数4:与参数3对应的单位
*/
ScheduledFuture<?> scheduledFuture= service.scheduleAtFixedRate(runnable1, 0,2, TimeUnit.SECONDS);
//方式二
ScheduledExecutorService service2 = Executors.newSingleThreadScheduledExecutor();
service2.scheduleAtFixedRate(runnable2, 1, 2, TimeUnit.SECONDS);
}
scheduleAtFixedRate() 按照固定频率
scheduleWithFixedDelay() 如果任务时间超过固定频率,按照任务实际时间延后
四、DelayQueue
DelayQueue是一个支持延时获取元素的阻塞队列, 内部采用优先队列PriorityQueue存储元素,同时元素必须实现Delayed 接口;在创建元素时可以指定多久才可以从队列中获取当前元素,只有在延迟期满时才能从队列中提取元素。
DelayQueue属于排序队列,它的特殊之处在于队列的元素必须实现Delayed接口,该接口需要实现
compareTo和getDelay方法
getDelay方法:获取元素在队列中的剩余时间,只有当剩余时间为0时元素才可以出队列。
compareTo方法:用于排序,确定元素出队列的顺序。
poll():获取并移除队列的超时元素,没有则返回空。
take():获取并移除队列的超时元素,如果没有则wait当前线程,直到有元素满足超时条件,返回结果。
public class DelayedTask implements Delayed {
// 任务的执行时间
private int executeTime = 0;
public DelayedTask(int delay) {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.SECOND, delay);
this.executeTime = (int) (calendar.getTimeInMillis() / 1000);
}
/**
* 元素在队列中的剩余时间
*
* @param unit
* @return
*/
@Override
public long getDelay(TimeUnit unit) {
Calendar calendar = Calendar.getInstance();
return executeTime - (calendar.getTimeInMillis() / 1000);
}
/**
* 元素排序
*
* @param o
* @return
*/
@Override
public int compareTo(Delayed o) {
long delay = this.getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
return delay == 0 ? 0 : (delay < 0 ? -1 : 1);
}
public static void main(String[] args) {
// 添加三个延迟任务(任务都是放在内存中)
DelayQueue<DelayedTask> queue = new DelayQueue<>();
queue.add(new DelayedTask(5));
queue.add(new DelayedTask(10));
queue.add(new DelayedTask(15));
System.out.println("start: " + System.currentTimeMillis() / 1000);
// 循环 从延迟队列中拉取任务
while (queue.size() != 0) {
DelayedTask delayedTask = queue.poll();
if (delayedTask != null) {
System.out.println("execute task: " + System.currentTimeMillis() / 1000);
}
//每隔2秒消费一次
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
五、Quartz实现
引入依赖
<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>
新建Job任务
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("hello quartz");
}
}
任务调度类
public static void main(String[] args) throws SchedulerException {
//创建Scheduler的工厂
SchedulerFactory sf = new StdSchedulerFactory();
//从工厂中获取调度器实例
Scheduler scheduler = sf.getScheduler();
/**
* 创建JobDetail
* withDescription:job的描述
* withIdentity:job的name和group
*/
JobDetail jb = JobBuilder.newJob(MyJob.class)
.withDescription("this is a job")
.withIdentity("myJob", "myJobGroup")
.build();
//任务运行的时间,5秒后启动任务
long time = System.currentTimeMillis() + 5 * 1000L;
Date statTime = new Date(time);
//创建Trigger,使用SimpleScheduleBuilder或者CronScheduleBuilder
Trigger t = TriggerBuilder.newTrigger()
.withDescription("this is a trigger")
.withIdentity("myTrigger", "myTriggerGroup")
//.withSchedule(SimpleScheduleBuilder.simpleSchedule())
//设置启动时间
.startAt(statTime)
//每隔3秒执行一次
.withSchedule(CronScheduleBuilder.cronSchedule("0/3 * * * * ? *"))
.build();
//注册任务和定时器
scheduler.scheduleJob(jb, t);
//启动 调度器
scheduler.start();
}
六、Cron表达式
Cron表达式是一个字符串,字符串以5或6个空格隔开,分为6或7个域,每一个域代表一个含义。从左至右,这些元素的定义如下:
1.秒(0–59)
2.分钟(0–59)
3.小时(0–23)
4.月份中的日期(1–31)
5.月份(1–12 或 JAN–DEC)
6.星期中的日期(1–7 或 SUN–SAT)
7.年份(1970–2099)
表达式的格式
Cron表达式的格式 : 秒 分 时 日 月 周 年(可选)
1.Seconds
2.Minutes
3.Hours
4.Day-of-Month
5.Month
6.Day-of-Week
7.Year(可选)
Seconds Minutes Hours DayofMonth Month DayofWeek Year
Seconds Minutes Hours DayofMonth Month DayofWeek
cron 表达式包括6/7部分:
Seconds 秒(0~59) :可出现 ,- * / 四个字符,有效范围为0-59的整数
Minutes 分钟(0~59):可出现 ,- * / 四个字符,有效范围为0-59的整数
Hours 小时(0~23):可出现 ,- * / 四个字符,有效范围为0-23的整数
DayofMonth 月中的天(1~31):可出现 ,- * / ? L W C八个字符,有效范围为1-31的整数
Month 月(1~12) :可出现 ,- * / 四个字符,有效范围为1-12的整数或JAN-DEc
DayofWeek 周中的天(MON,TUE,WED,THU,FRI,SAT,SUN,或数字1~7 1表示MON,依次类推):可出现 ,- * / ? L C #四个字符,有效范围为1-7的整数或SUN-SAT两个范围。1表示星期天,2表示星期一, 依次类推
Year 年(1970-8099)::可出现 ,- * / 四个字符,有效范围为1970-2099年
表达式的特殊字符
/ 表示指定数值的增量,从起始时间开始触发,然后每隔固定时间触发一次
如在Minutes域使用5/10,则意味着5分钟触发一次,而15,25等分别触发一次.
* 表示匹配该域的任意值,如在Minutes域使用*,即表示每分钟都会触发事件。
- 表示区间范围,如在Minutes域使用5-10,表示从5分到10分钟每分钟触发一次
, 表示列出枚举值,如在Minutes域使用5,10,则意味着在5和10分钟触发一次。
? 表示不指定值,只能用在月中的天(DayofMonth)和周中的天(DayofWeek)两个域,DayofMonth和DayofWeek会相互影响。
如每月1日触发,不管1日是星期几,只能这样写: * * * 1 * ?,最后一位只能用?不能使用*,如果使用*表示不管星期几都会触发,实际上并不是这样。
L 表示 day-of-month和day-of-week域,但在两个字段中意思不同,在day-of-month域中表示:一个月的最后一天 在day-of-week域中表示"7"或者"SAT",如果在day-of-week域前加上数字,表示一个月的最后几天,如:"6L"表示一个月的最后一个星期五
W 只允许日期域出现,用于指定日期的最近工作日.如:在日期域中写"15W",表示这个月15号最近的工作日,所以如果15号是周六则任务会在14号触发,如果15号是周日,则任务会在周一也就是16号触发.如果日期域填写"1W"即使1号是周六,那么任务也只会在下周一,也就是3号触发,"W"字符指定最近工作日是不能够跨月的,字符"W"只能配合一个单独的数值使用不能是一个数字段,如1-12W是错误的
LW L和W可以在日期域中联合使用 LW表示这个月最后一周的工作日
# 只允许在星期域中出现 这个字符用于指定本月的某某天 如:"6#3"表示本月第三周的星期五(6表示星期五,3表示周) "2#1"表示本月第一周的星期一 "4#5"表示第五周的星期三
C 允许在日期域和星期域出现 这字符依靠一个指定的"日历" 也就是说这个表达式的值依赖于相关的"日历"的计算结果 如果没有"日历"关联 ,则等价于所有包含"日历" 如: 日期域是5C表示关联"日历"中第一天,或者这个月开始的第一天的后5天,星期域是"1C"表示关联"日历""中第一天,或者星期的第一天的后1天,也就是周日的后一天(周一)
表达式的示例
表达式 | 含义 |
---|---|
0/2 * * * * * | 每隔2秒执行 |
0 0/2 * * * * | 每隔2分钟执行 |
0 0 0 * * * | 表示每天0点执行 |
0 0 12 ? * WEN | 每周三12点执行 |
0 20 8 ? * MON-FRI | 每月的周一到周五8点 20分执行 |
0 20 8 ? * MON,FRI | 每月的周一和周五8点 20分执行 |
0 0 10,14,16 * * ? | 每天上午10点,下午2点,4点 |
0 0/30 9-17 * * ? | 朝九晚五工作时间内每半小时 |
0 0 12 ? * WED | 表示每个星期三中午12点 |
0 0 12 * * ? | 每天中午12点触发 |
0 15 10 ? * * | 每天上午10:15触发 |
0 15 10 * * ? | 每天上午10:15触发 |
0 15 10 * * ? * | 每天上午10:15触发 |
0 15 10 * * ? | 2022 2022年的每天上午10:15触发 |
0 * 14 * * ? | 在每天下午2点到下午2:59期间的每1分钟触发 |
0 0/5 14 * * ? | 在每天下午2点到下午2:55期间的每5分钟触发 |
0 0/5 14,17 * * ? | 在每天下午2点到2:55期间和下午5点到5:55期间的每5分钟触发 |
0 0-5 14 * * ? | 在每天下午2点到下午2:05期间的每1分钟触发 |
0 10,44 14 ? 3 WED | 每年三月的星期三的下午2:10和2:44触发 |
0 15 10 ? * MON-FRI | 周一至周五的上午10:15触发 |
0 15 10 15 * ? | 每月15日上午10:15触发 |
0 15 10 L * ? | 每月最后一日的上午10:15触发 |
0 15 10 ? * 6L | 每月的最后一个星期五上午10:15触发 |
0 15 10 ? * 6L 2020-2022 | 2020年至2022年的每月的最后一个星期五上午10:15触发 |
0 15 10 ? * 6#3 | 每月的第三个星期五上午10:15触发 |
七、Spring Task实现定时任务
SpringTask使用Cron表达式时,只能写6个域!
XML配置实现
1.添加依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
2.定义定时任务
新建类,添加自动注入的注解,定义定义任务的方法
@Component
public class TaskJob {
public void job1(){
System.out.println("任务 1:" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
}
public void job2(){
System.out.println("任务 2:" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
}
}
3. 配置文件spring.xml
在src/main/resources
目录下新建配置文件spring.xml,并设置Spring扫描
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task.xsd">
<!-- Spring扫描注解配置 -->
<context:component-scan base-package="cn.ybzy.demo" />
<!--配置定时规则: ref:指定任务类;method:指定运行的方法;cron:cronExpression表达式 -->
<task:scheduled-tasks>
<!-- 每个两秒执行一次任务 -->
<task:scheduled ref="taskJob" method="job1" cron="0/2 * * * * ?"/>
<!-- 每隔五秒执行一次任务 -->
<task:scheduled ref="taskJob" method="job2" cron="0/5 * * * * ?"/>
</task:scheduled-tasks>
</beans>
4.测试定时任务
public static void main( String[] args ) {
// 获取Spring上下文环境
ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
// 获取指定Bean对象
TaskJob taskJob = (TaskJob) ac.getBean("taskJob");
}
注解配置实现
1.定义定时任务
@Component
public class TaskJob02 {
@Scheduled(cron="0/2 * * * * ?")
public void job1(){
System.out.println("任务 1:" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
}
@Scheduled(cron="0/5 * * * * ?")
public void job2(){
System.out.println("任务 2:" + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
}
}
2.配置定时任务驱动
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task.xsd">
<!-- Spring扫描注解配置 -->
<context:component-scan base-package="cn.ybzy.demo" />
<!-- 配置定时任务驱动。开启这个配置,spring才能识别@Scheduled注解 -->
<task:annotation-driven />
</beans>
3.测试定时任务
public static void main( String[] args ) {
// 获取Spring上下文环境
ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
// 获取指定Bean对象
TaskJob02 taskJob02 = (TaskJob02) ac.getBean("taskJob02");
}
Spring Boot
在Spring boot启动类上添加注解:
@EnableScheduling
@Component
public class SpringTaskTest {
private static final Logger LOGGER = LoggerFactory.getLogger(SpringTaskTest.class);
/**
* 每隔2秒执行一次
*/
@Scheduled(cron = "0/2 * * * * *")
public void task1() {
LOGGER.info("--------------------task1开始--------------------");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LOGGER.info("--------------------task1结束--------------------");
}
}
串行任务与并行任务
除了使用cron表达式,还支持如下用法
@Scheduled(fixedRate = 2000) 上次执行开始时间后2秒执行
@Scheduled(fixedDelay = 2000) 上次执行完毕后2秒执行
@Scheduled(initialDelay=2000, fixedRate=5000) 第一次延迟2秒,以后每隔5秒执行一次
1.串行任务
两个任务方法由一个线程串行执行,task1方法执行完成task2再执行。
/**
* 每隔2秒执行一次
*/
@Scheduled(cron = "0/2 * * * * *")
public void task1() {
LOGGER.info("--------------------task1开始--------------------");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LOGGER.info("--------------------task1结束--------------------");
}
/**
* 上次执行开始时间后5秒执行
*/
@Scheduled(fixedRate = 5000)
public void task2(){
LOGGER.info("--------------------task2开始--------------------");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LOGGER.info("--------------------task2结束--------------------");
}
两个任务交替执行,即任务串行执行.
-------------------task2开始--------------------
--------------------task2结束--------------------
-------------------task1开始--------------------
-------------------task1结束--------------------
--------------------task2开始--------------------
--------------------task2结束--------------------
--------------------task1开始--------------------
-------------------task1结束--------------------
2.并行任务
创建添加任务配置类,需要配置线程池实现多线程调度任务
@Configuration
//启动类 or 此处配置@EnableScheduling
public class TaskConfig implements SchedulingConfigurer, AsyncConfigurer {
/**
* 线程池线程数量
*/
private int poolSize = 5;
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
//创建定时任务线程池
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
///初始化线程池
scheduler.initialize();
//线程池容量
scheduler.setPoolSize(poolSize);
return scheduler;
}
@Override
public Executor getAsyncExecutor() {
Executor executor = taskScheduler();
return executor;
}
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return null;
}
@Override
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
scheduledTaskRegistrar.setTaskScheduler(taskScheduler());
}
}
添加任务配置类后,任务并行执行.
--------------------task2开始--------------------
-------------------task1开始--------------------
-------------------task2结束--------------------
--------------------task1结束--------------------
-------------------task1开始--------------------
-------------------task2开始--------------------
--------------------task1结束--------------------
--------------------task2结束--------------------
八、分布式定时任务之XXL-JOB
篇幅过长,参考:分布式定时任务之XXL-JOB
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/137064.html