引言:知易行难
这里我推荐使用第一种,Spring定时任务,简单又简介,高效
一、Spring定时任务
基于springboot创建一个项目,使用定时任务很简单
俩步即可实现
1、启动类上加注解@EnableScheduling注解开启定时任务
2、方法上加注解@Scheduled设置任务执行时间
示例 默认是单线程的定时任务
@Scheduled(fixedDelay =5*1000 ) // 每隔五秒执行一次 单位毫秒 1秒 = 1000毫秒
public void sendMsg(){
System.out.println("当前线程名:"+Thread.currentThread().getName());
System.err.println(DateFormat.getDateTimeInstance().format(new Date()) +"\t多喝热水");
}
这里设置的是 时间间隔 fixedDelay 和 fixedRate 效果等同
fixedRate和fixedDelay区别:
fixedRate:它的间隔时间是根据上次任务开始的时候计时。
例如:fixedRate = 5 * 1000 执行该方法所花的时间是2秒,那么3秒后就会再次执行该方法。
fixedDelay:它的间隔时间是根据上次的任务结束的时候开始计时的。
例如:一个方法设置了 fixedDelay = 5*1000 当该方法某一次执行结束后,开始计算时间,当时间达到5秒,就开始再次执行该方法。
多线程实现,也很简单
1、启动类上开启异步注解 @EnableAsync
2、在定时任务的方法上加注解,设置异步执行 @Async
示例:每三秒执行一次
@Scheduled(cron ="0/3 * * * * ?")
@Async
public void ser(){
System.out.println("当前线程名:"+Thread.currentThread().getName());
System.err.println(DateFormat.getDateTimeInstance().format(new Date())+"\t每3秒的时候执行");
}
cron表达式
在线生成cron表达式的网站:在线cron表达式
只要知道 , - * / 这四个和 ? 号 这五个就可以了,其他的了解一下即可
通用符号: , - * /
逗号表示枚举值,例如:在Minutes域使用5,20 表示在分钟数为5, 20的时候触发事件
减号表示范围, 例如:在Minutes域使用5-20 表示在分钟数为5到20的时候每分钟都触发一次事件
*号表示该域的任意值,假如在Minutes域使用* 表示分钟数不受限制,每分钟都触发事件
/号表示起始时间开始触发,然后每隔固定时间触发一次,例如在Minutes域使用5/20表示时间的分钟数为5的时候触发一次,后隔20分钟触发一次,即 25触发执行一次、45触发执行一次。
专有符号:在Spring定时任务中,除了问号,其他都不支持!
? 问号:只能用在日和星期俩个域,他俩互斥,必须对其中一个进行设置,使用的场景不关心这个值
L 大写字母L,只能出现在日和星期俩个域,如果在DayOfWeek使用5L,意味着在最后的一个星期四触发
W 大写字母W,表示有效工作日(周一到周五),只能出现在DayOfMonth域,系统将在离指定日期的最近有效工作日触发事件
LW 这俩个字符可以连用,表示在某个月最后一个工作日
# 用于确定每个月的第几个星期几,只能出现在DayofWeek域,例如:4#2,表示某月的第二个星期三
C 只能用在DayofMonth和DayofWeek俩个域,需要关联日历,如果没关联可以忽略。
拓展一下:
@Scheduled()的8个参数的意思
1、cron
接受一个cron表达式
2、zone
时区,接受一个java.util.TimeZone#ID
默认是一个空字符串,取服务器所在地的时区,该字段一般留空,我们一般使用的时区:Asia/Shanghai
3、fixedDelay
上一次执行完 间隔多长时间再次执行
4、fixedDelayString
和fixedDelay 意思相同,只是使用字符串的形式,唯一不同的是支持占位符
5、fixedRate
上一次开始执行时间点之后多长时间再执行
6、fixedRateString
与fixedRate 意思相同,只是使用字符串的形式,唯一不同的是支持占位符
7、initialDelay
第一次延迟多长时间后再执行
8、initialDealyString
与initialDelay 意思相同,只是使用字符串的形式,唯一不同的是支持占位符
二、JDK自带的Timer
使用也是两步
1、创建Timer对象
2、执行schedule方法
示例1
public static void main(String[] args) {
// 创建timer对象
Timer timer = new Timer();
// 执行定时任务
// 参数1 timerTask对象 定时任务对象
// 参数2 任务什么时候启动
// 参数3 执行任务的时间间隔
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("定时任务1===\t"+ DateFormat.getDateTimeInstance().format(new Date()));
}
},new Date(),1000);
}
示例2
public static void main(String[] args) {
// 创建timer对象 任务启动
Timer timer = new Timer();
for (int i = 0; i < 3; i++) {
StudyTimerTask studyTimerTask = new StudyTimerTask("执行者"+i+"号");
// 立刻执行 每隔2秒执行一次 任务添加
timer.schedule(studyTimerTask,new Date(),2000);
// timer.schedule(); 丢任务 少执行
// timer.scheduleAtFixedRate(); 提前执行,执行时间会乱
// 单线程 任务阻塞 任务超时
}
}
//另一个类
class StudyTimerTask extends TimerTask{
private String name;
public StudyTimerTask(String name) {
this.name = name;
}
@Override
public void run() {
try {
System.out.println(name+"\t任务开始时间\t"+DateFormat.getDateTimeInstance().format(new Date()));
Thread.sleep(5000);
System.err.println(name+"\t任务结束时间\t"+DateFormat.getDateTimeInstance().format(new Date()));
// 线程池执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
多线程模拟示例
示例3
public static void main(String[] args) {
// 线程池执行
// 先创建一个线程池
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 2; i++) {
StudyTimerTask2 task2 = new StudyTimerTask2("多线程启动" + i + "号");
// 参数1 任务 参数2 延迟时间 参数3 间隔时间 参数4 时间单位
scheduledThreadPool.scheduleAtFixedRate(task2,0,2, TimeUnit.SECONDS);
}
}
class StudyTimerTask2 implements Runnable{
private String name;
public StudyTimerTask2(String name) {
this.name = name;
}
@Override
public void run() {
try {
System.out.println(name+"\t任务开始时间\t"+DateFormat.getDateTimeInstance().format(new Date()));
Thread.sleep(5000);
System.err.println(name+"\t任务结束时间\t"+DateFormat.getDateTimeInstance().format(new Date()));
// 线程池执行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
三、quartz框架
这里我记录一下源生的quartz的使用
使用详情
1、创建一个类实现Job
2、调用(JobDetail 和Trigger) 这个比较复杂一点
导入依赖
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
</dependency>
@DisallowConcurrentExecution和@PersistJobDataAfterExecution的解释:
禁止并发地执行同一个job定义(jobDetail定义的多个实例) 加在类上面
@DisallowConcurrentExecution
如果一个任务不是持久化的,则当没有触发器关联它的时候,quartz会从scheduled删除它
将参数进行持久化(对trigger中的dataMap无效)
@PersistJobDataAfterExecution
创建一个job
@DisallowConcurrentExecution
@PersistJobDataAfterExecution
public class MyJob implements Job {
// 另一种存值的方式
private String address;
public void setAddress(String address) {
this.address = address;
}
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
JobDataMap jobDetailMap = jobExecutionContext.getJobDetail().getJobDataMap();
JobDataMap jobTriggerMap = jobExecutionContext.getTrigger().getJobDataMap();
jobDetailMap.put("count",jobDetailMap.getIntValue("count")+1);
System.out.println("jobDetailMap=\t"+jobDetailMap.getString("name"));
System.out.println("jobDetailMap=\t"+jobDetailMap.getString("age"));
System.out.println("jobTriggerMap=\t"+jobTriggerMap.getString("sex"));
// 要是有键名相同的话 这里会覆盖掉
JobDataMap mergedJobDataMap = jobExecutionContext.getMergedJobDataMap(); // 就是把前俩个的map都获取
System.err.println("address=\t"+address);
System.err.println("调的次数count=\t"+jobDetailMap.getInt("count"));
System.err.println("mergedJobDataMap\tsex="+mergedJobDataMap.get("sex")+"\tname="+mergedJobDataMap.get("name"));
System.err.println("MyJob is execute\t"+ DateFormat.getDateTimeInstance().format(new Date()));
// 测试每次调用任务的时候都创建一个新的实例
System.out.println("jobDetail:\t"+System.identityHashCode(jobExecutionContext.getJobDetail()));
System.out.println("job:\t"+System.identityHashCode(jobExecutionContext.getJobInstance()));
}
}
启动
使用usingJobData 进行传参,可以理解为往map中添加键值对
public class TestJob {
public static void main(String[] args) {
int count=0; // 统计定时任务调了几次
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("任务名叫小明","任务组为group1")
// 理解为启动定时任务的时候 往业务逻辑传递一些参数
.usingJobData("name","张三") // 存放map 键值的形式 任务可以获取参数
.usingJobData("age","18")
.usingJobData("count",count)
.usingJobData("address","上海浦东区")
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("触发器1","触发器组1")
.usingJobData("sex","男") // 设置键值
// Job 通过set设置值的话, trigger的值会覆盖jobDetail的值
.usingJobData("address","北京市")
.startNow() // 立即启动 指定时间启动的话 startAt
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1)
.repeatForever()) // 一直执行
.build();
try {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}