项目中用到过定时器的知识点,发现其实并不简单,在此总结下。关于定时目前2种技术能解决,一种是轻量级的Java Timer,另一种是重量级的Quartz。
Timer是jdk中提供的一个定时器工具,使用的时候会在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次。TimerTask是一个实现了Runnable接口的抽象类,代表一个可以被Timer执行的任务。
关于Timer一个简单的例子:
Timer timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
System.out.println("11232");
}
}, 200000 , 1000);
schedule是一个定时调度的方法,里面有多个方法,每个方法代表的意义不同,例如当前的Demo表示的是200秒后开始执行,每一秒执行一次。Timer提供的定时调度的方法很多,能满足多种场景:
1、这个方法是调度一个task,经过delay(ms)后开始进行调度,仅仅调度一次。
public void schedule(TimerTask task, long delay)
2、在指定的时间点time上调度一次。
public void schedule(TimerTask task, Date time)
3、这个方法是调度一个task,在delay(ms)后开始调度,每次调度完后,最少等待period(ms)后才开始调度。
public void schedule(TimerTask task, long delay, long period)
4、和上一个方法类似,唯一的区别就是传入的第二个参数为第一次调度的时间。
public void schedule(TimerTask task, Date firstTime, long period)
5、调度一个task,在delay(ms)后开始调度,然后每经过period(ms)再次调度,貌似和方法:schedule是一样的,其实不然,后面你会根据源码看到,schedule在计算下一次执行的时间的时候,是通过当前时间(在任务执行前得到) + 时间片,而scheduleAtFixedRate方法是通过当前需要执行的时间(也就是计算出现在应该执行的时间)+ 时间片,前者是运行的实际时间,而后者是理论时间点,例如:schedule时间片是5s,那么理论上会在5、10、15、20这些时间片被调度,但是如果由于某些CPU征用导致未被调度,假如等到第8s才被第一次调度,那么schedule方法计算出来的下一次时间应该是第13s而不是第10s,这样有可能下次就越到20s后而被少调度一次或多次,而scheduleAtFixedRate方法就是每次理论计算出下一次需要调度的时间用以排序,若第8s被调度,那么计算出应该是第10s,所以它距离当前时间是2s,那么再调度队列排序中,会被优先调度,那么就尽量减少漏掉调度的情况。
public void scheduleAtFixedRate(TimerTask task, long delay, long period)
6、方法同上,唯一的区别就是第一次调度时间设置为一个Date时间,而不是当前时间的一个时间片,我们在源码中会详细说明这些内容。
public void scheduleAtFixedRate(TimerTask task, Date firstTime,long period)
关于Timer在实际使用方面,和java多线程一样有两种方式:
一种是通过多线程的方式运行:
package cn.xdf.dtmanager.quartz;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimerTask;
/**
* Created by fengch on 2017/9/21.
*/
public class MyTask extends TimerTask {
private String name;
private int age;
public MyTask(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void run() {
SimpleDateFormat sdf = null;
sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
System.out.println("当前时间:" + sdf.format(new Date()) + "---------------------" + this.name + ":::" + this.age);
}
}
Timer t = new Timer(); // 建立Timer对象
t.schedule( new MyTask("多吃点", 1), DateUtil.StringToDate(time1,DateUtil.DATATIME_FORMMATER));
另一种是通过匿名类不类的方式(java8可以通过lambda方式):
就是如上所写的一个demo!
quartz是一个很好的定时job框架,在时间节点配置方面特别的好。所以关于Quartz方面的内容也是特别的多,笔者在这里也是班门弄斧。在多机器部署的时候,可想而知要是一个多个定时都被执行一次,会产生很多错误的可能。所以本篇只介绍使用Quartz这是为leader模式!
多台服务器部署时候,只有其中一台服务器是leader,只有当leader主机挂了的时候,另外一台following并自动成为主机。quartz分三个线程进行完成leader模式的设计:
一、集群任务调度线程
此任务调度的作用是:修改当前服务器的时间为系统的当前时间,将过期的服务状态变更为停止:
package com.lgy.core.task;
import com.lgy.comm.Constant;
import com.lgy.core.base.AbstractTask;
import com.lgy.service.ServInfoService;
import org.apache.log4j.Logger;
import org.quartz.JobExecutionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 集群任务调度线程的定时任务类
* @author yuejing
* @date 2015年3月29日 下午10:05:34
* @version V1.0.0
*/
@Component
public class MainTask extends AbstractTask {
private static final Logger LOGGER = Logger.getLogger(MainTask.class);
@Autowired
private ServInfoService servInfoService;
@Override
public void execute(JobExecutionContext context) {
System.out.println("=========================== 执行了MainTask ====================");
//=========================== 发送任务心跳(间隔10s) begin ====================
//修改当前服务的updatetime为当前时间
servInfoService.updateUpdatetimeByServid(Constant.serviceCode());
//将过期的服务状态变更为停止
servInfoService.updateDestroy();
//=========================== 发送任务心跳(间隔10s) end ====================
}
}
二、leader选举调度线程
流程图如下:
三、清除过期调度线程
此任务调度的作用是:删除过期已经销毁的服务,将已经销毁的服务设置为非leader:
package com.lgy.core.task;
import com.lgy.core.base.AbstractTask;
import com.lgy.service.ServInfoService;
import com.lgy.utils.FrameTimeUtil;
import org.apache.log4j.Logger;
import org.quartz.JobExecutionContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* 清空任务日志的定时任务类
* @author yuejing
* @date 2015年3月29日 下午10:05:34
* @version V1.0.0
*/
@Component
public class CleanTask extends AbstractTask {
private static final Logger LOGGER = Logger.getLogger(CleanTask.class);
@Autowired
private ServInfoService servInfoService;
@Override
public void execute(JobExecutionContext context) {
System.out.println("=========================== CleanTask ====================");
String siValue = "7";
Date siDate = FrameTimeUtil.addDays(FrameTimeUtil.getTime(), - Integer.valueOf(siValue));
//清空小于指定日期的已停止的服务
servInfoService.deleteDestroyLtDate(siDate);
//修改已销毁的服务为非Leader
servInfoService.destroyLeader();
}
}
有兴趣参考:
https://gitee.com/yuejing/task