java简单实现定时任务
- 使用Timer
- 使用ScheduledThreadPool
- 使用Spring quartz
使用Timer
IDEA、 JDK1.8、 Spring boot
demo代码如下:
package com.momomian.learn.code.admin.task;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
@Component
public class DemoTimerTask {
private Timer timer;
private boolean timerRunning = false;
@PostConstruct
public void init(){
timer = new Timer("Demo-Timer-Task");
Date firstTime = getFirstTime();
timer.schedule(new TimerTask() {
@Override
public void run() {
timerRunning = true;
try {
performingTasks();
} catch (Exception e) {
e.printStackTrace();
}
timerRunning = false;
}
}, firstTime, 1000);
}
public Date getFirstTime(){
Calendar calendar = Calendar.getInstance();
calendar.setTime(new Date());
calendar.add(Calendar.DAY_OF_MONTH, 0);
calendar.set(Calendar.HOUR_OF_DAY, 0);
calendar.set(Calendar.MINUTE, 0);
calendar.set(Calendar.SECOND, 0);
return calendar.getTime();
}
public void performingTasks(){
System.out.println("Are you ok?"+System.currentTimeMillis());
}
}
解析:
timer.schedule方法接受一个TimerTask 对象,firstTime是其第一次触发的时间。如果程序运行时的时间大于等于这个时间,那么就会执行任务。period是指定的时间间隔,单位是毫秒。
public void schedule(TimerTask task, Date firstTime, long period) {…}
getFirstTime方法是获取一个初次执行时间。你可以用calendar.set方法设置具体时间,或者add方法增加一个相对的时间。最后得到初次执行的时间。
我们设定间隔为1000毫秒,从运行的结果看来,每秒钟执行一次,似乎延迟约半毫秒到一毫秒
。这种延迟是叠加的。时间拖得越长,与预定的执行之间可能会造成偏差。
如果任务时间超过了我们设定的间隔时间,Timer会怎么处理呢?我对timer对象在此执行一个schedule方法去new一个TimerTask 作为定时任务2。发现在这个任务没有执行之前,并不会去执行定时任务1。也就是说会导致定时任务1没有按时执行任务。这是因为一个Timer只开启一个线程
。
你自然可以每次一个新的任务就去写一个类创建一个新的Timer,这样就可以避免这个问题。
但是注意看看你的项目中用了多少Timer,如果你处理的不好,太多的线程数将可能让你的系统崩溃。我们需要一个线程池去管理。
从阿里巴巴的代码规范看它要求我们使用ScheduledThreadPool去做定时任务。
如果你正在使用IDEA,并且使用了Alibaba Java Coding Guidelines
插件,你在编写上述代码的时候Timer就会出现一个提示:
多线程并行处理定时任务时,Timer运行多个TimeTask时,只要其中之一没有捕获抛出的异常,其它任务便会自动终止运行,使用ScheduledExecutorService则没有这个问题。
实验做一下很简单,在上述代码中再增加一个schedule执行。
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("strat");
timerRunning = true;
performingTasks2();
timerRunning = false;
}
}, firstTime, 1);
这里我们不去try,catch,那么出现异常的时候,这个timer将停止工作。
public void performingTasks2(){
String name = null;
String surname = getSurname(name);
System.out.println(surname);
}
public String getSurname(String name){
String[] split = name.split("-");
return split[0];
}
使用ScheduledThreadPool
从阿里的规约上,给出了一个例子:
//org.apache.commons.lang3.concurrent.BasicThreadFactory
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder().namingPattern(“example-schedule-pool-%d”).daemon(true).build());
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
//do something
}
},initialDelay,period, TimeUnit.HOURS);
其中BasicThreadFactory类来自:org.apache.commons.lang3.concurrent.BasicThreadFactory
maven添加依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8</version>
</dependency>
根据规约的提示我们写一个简单的demo (JDK1.8+)
@Component
public class ScheduledThreadPoolTaskTimer {
@PostConstruct
public void init(){
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder().namingPattern("task-timer-pool-%d").daemon(true).build());
executorService.scheduleAtFixedRate(this::performingTasks,0,1000, TimeUnit.MILLISECONDS);
}
public void performingTasks(){
System.out.println("Are you ok?"+System.currentTimeMillis());
}
}
这里我们使用了BasicThreadFactory去创建线程,也就是采用线程池的方式。scheduleAtFixedRate方法参数如下
- @param command 要执行的任务
- @param initialDelay 延迟首次执行的时间
- @param period 连续执行之间的时间间隔
- @param unit initialDelay和period参数的时间单位
我们同样设定间隔为1000毫秒,
结果可以看出,这里的间隔时间虽然有一个毫秒的误差,但是没有误差的叠加性。
并且我们可以做之前的测试。
@PostConstruct
public void init(){
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder().namingPattern("task-timer-pool-%d").daemon(true).build());
executorService.scheduleAtFixedRate(this::performingTasks,0,1000, TimeUnit.MILLISECONDS);
executorService.scheduleAtFixedRate(this::performingTasks2,0,1000, TimeUnit.MILLISECONDS);
}
public void performingTasks(){
System.out.println("Are you ok?"+System.currentTimeMillis());
}
public void performingTasks2(){
String name = null;
String surname = getSurname(name);
System.out.println(surname);
}
public String getSurname(String name){
String[] split = name.split("-");
return split[0];
}
发现尽管第二个任务异常没有捕获,没有执行。但是第一个任务依然正常执行。
ScheduledThreadPoolExecutor new的时候使用的构造方法可以传入一个corePoolSize。用于指定线程池的可以容纳多少线程。
使用Spring quartz
如果你使用的是spring boot,可以用这个方案
首先在入口类中添加注解@EnableScheduling
@SpringBootApplication
@EnableScheduling
public class AdminApplication {
public static void main(String[] args) {
SpringApplication.run(AdminApplication.class,args);
}
}
实现类demo:
@Component
public class QuartzTask {
public boolean isAllowRunEat = true ;
@Scheduled(cron = "*/5 * * * * ?")
public void taskToEat(){
try{
if(isAllowRunEat){
performingEatTask();
}
}catch (Exception e){
e.printStackTrace();
}
}
@Scheduled(cron = "*/10 * * * * ?")
public void taskToDrink(){
try{
performingDrinkTask();
}catch (Exception e){
e.printStackTrace();
}
}
public void performingEatTask(){
System.out.println("eat eat eat "+System.currentTimeMillis());
}
public void performingDrinkTask(){
System.out.println("drink drink drink "+System.currentTimeMillis());
}
}
这种方式是十分简单的,也是避免了Timer的问题,唯一需要讲解的是cron 表达式。这个是一个时间相关的表达式:
由至少6个时间元素组成,用空格分开。从左到右是:秒(0-59)、分(0-59)、时(0-23)、日(0-31)、月(0-11)、星期(1-7)、年(1970-2099)
其中其中月份中的日和星期中的日存在冲突,选择其一配置即可。
其中每个元素可以是:
1:值 如:1
2:连续区间 表达式 : n-m
3:间隔时间 /n 表示每隔n…
4:列表 (1,2,3)
5:通配符
注意:?号仅用于天月和天星期,表示不指定
例子:
1、0 0 5 * * ? 每天凌晨5点执行一次
2、 0 0 1,3,4 * * ? 每天的1点3点4点执行一次
3、*/5 * * * * ? 每隔5秒生成一次
这个表达式可以说是很强大了。懒得学的人可以使用以下网站:
cron表达式在线生成链接
isAllowRunEat 这个变量的目的是控制定时任务的触发条件。如果你有需求通过其他方法去开启/关闭这个任务,你可以将其配置到配置文件中,或者你有需求可以将其在后台实现可以人工配置。这是需求允许情况下的简单方案。
到这里我们已经学会了三种定时任务简单的使用。我这边写的例子都是简单的。你应该为你的项目需求考虑更加优雅的实现方式,实现重用。像上面一个类中写两个任务是不可取的方式。保证单一职责是重要的。
更加完善的方案可以参考:
懂点IT 的文章,链接附上:
草草结尾。我要回家偷懒,拜拜。
分享个人喜欢的句子:
胜利…失败…这些词语没有任何意义。生命位于这些意象之下,并且已经准备好新的意象。一场胜利削弱了一群怀抱着同样信仰的人,一场败仗却唤醒了另一群。-让-保罗·萨特