Java定时器Timer的使用
定时计划任务功能在Java中主要使用的就是Timer对象,它在内部使用多线程的方式进行处理,所以它和多线程技术还是有非常大的关联的。
1、看下面代码:
1 public void TestUserNvrConnectOnline() {
2 Timer timer = new Timer(true);
3 timer.schedule(new TimerTask() {
4 public void run() {
5 //你要定时执行的功能
6 isUserNvrConnectLast();
7 }
8 }, 0, 60 * 1000);
9
10 }
周期执行任务,Timer.schedule(TimerTask task,long firstTime,long period),参数task是要执行代码的主体,参数firstTime是Timer在系统启动后第一次运行的时间毫秒值,参数period是代码循环运行的时间间隔,每次最少等待period执行一次。
2、Timer还有其他几种使用方式,基本上大同小异,比如:
private Set<Integer> unitTimeSet = new HashSet<>(24);//每天更新整点数,把已经检查的时间记录下来
1 public void checkDeviceDeopped() {
2 Calendar calendar = Calendar.getInstance();
3 //此时要在 第一次执行定时任务的时间加一小时,以便此任务在下个时间点执行。
4 calendar.add(Calendar.HOUR_OF_DAY,1);
5 calendar.set(Calendar.MINUTE, 0);
6 calendar.set(Calendar.SECOND, 0);
7 java.util.Date date = calendar.getTime(); //第一次执行定时任务的时间
8
9 Timer timer = new Timer(true);
10 timer.schedule(new TimerTask() {
11 public void run() {
12 Calendar cal = Calendar.getInstance();
13 int hour = cal.get(Calendar.HOUR_OF_DAY);
14
15 if (hour == 0){
16 unitTimeSet.clear();
17 }
18 if (hour % unitTime == 0 && !unitTimeSet.contains(hour)){
19 Session session = sessionFactory.openSession();
20 cal.set(Calendar.MINUTE, 0);
21 cal.set(Calendar.SECOND, 0);
22 java.util.Date endTime = cal.getTime();
23 cal.add(Calendar.HOUR_OF_DAY,-1);
24 java.util.Date startTime = cal.getTime();
25 for (String deviceId : mapNvr.keySet()) {
26 //查询设备在指定时间内的掉线次数
27 Integer offLineNum = 0;
28 try {
29 offLineNum = (Integer) session.createQuery("SELECT COUNT(*) FROM SystemLog WHERE workerid= :deviceId AND dt<= :endTime AND dt>= :startTime ").setParameter("deviceId", deviceId)
30 .setParameter("endTime", endTime, TemporalType.TIMESTAMP).setParameter("startTime", startTime, TemporalType.TIMESTAMP).uniqueResult();
31 }catch (Exception e){
32 e.printStackTrace();
33 }
34
35 if (offLineNum >= 10){
36 //发送邮件
37 try {
38 CompletableFuture.runAsync(() -> {
39 sendMail("NUM", deviceId);
40 });
41 } catch (Exception e) {
42 e.printStackTrace();
43 }
44 }
45 }
46 }
47 unitTimeSet.add(hour);
48 }
49 }, date,60 * 60 * 1000);
50 }
上面的代码是在每天的整点执行一次定时任务,查看设备离线次数,如果超过10次就发送邮件,循环时长和离线次数可另外定义变量进行控制,发送邮件调用了异步,感兴趣的朋友可以查看我的另外文章Java异步CompletableFuture的使用。
3、另外形式的定时器不再上代码,大体如下:
1)、public void schedule(TimerTask task, long delay)
经过时长delay后执行,仅执行一次。
2)、public void schedule(TimerTask task, Date time)
在指定的时间点time执行,仅执行一次。
3)、public void scheduleAtFixedRate(TimerTask task, long delay, long period)
调用一个task,第一次在delay时长后执行,以后每次经过period执行一次,看起来和schedule这种方式一样,实际是有区别的。schedule在计算下一次执行的时间的时候,是通过当前执行的时间(在任务执行前得到) + 时间片period,而scheduleAtFixedRate方法是通过当前需要执行的时间(计算出现在应该执行的时间)+ 时间片,前者是程序运行的实际时间,是动态的,而后者是指定的理论时间点,代码编译后就是固定不变的。我们可以看一下源码部分,schedule(TimerTask task, long delay,long period)的源码如下:
public void schedule(TimerTask task, long delay, long period) {
if (delay < 0)
throw new IllegalArgumentException("Negative delay.");
if (period <= 0)
throw new IllegalArgumentException("Non-positive period.");
sched(task, System.currentTimeMillis()+delay, -period);
}
再看scheduleAtFixedRate(TimerTask task, long delay, long period)的源码如下:
1 public void scheduleAtFixedRate(TimerTask task, long delay, long period) {
2 if (delay < 0)
3 throw new IllegalArgumentException("Negative delay.");
4 if (period <= 0)
5 throw new IllegalArgumentException("Non-positive period.");
6 sched(task, System.currentTimeMillis()+delay, period);
7 }
看起来没有区别,唯一的区别就是sched(task, System.currentTimeMillis()+delay, period)的第三个参数,schedule加了一个负号,其实就是在调用sched方法时用来区分schedule和scheduleAtFixedRate的。我们看sched的代码如下:
1 private void sched(TimerTask task, long time, long period) {
2 if (time < 0)
3 throw new IllegalArgumentException("Illegal execution time.");
4
5 synchronized(queue) {
6 if (!thread.newTasksMayBeScheduled)
7 throw new IllegalStateException("Timer already cancelled.");
8
9 synchronized(task.lock) {
10 if (task.state != TimerTask.VIRGIN)
11 throw new IllegalStateException(
12 "Task already scheduled or cancelled");
13 task.nextExecutionTime = time;
14 task.period = period;
15 task.state = TimerTask.SCHEDULED;
16 }
17
18 queue.add(task);
19 if (queue.getMin() == task)
20 queue.notify();
21 }
22 }
queue是一个队列,他在做这个操作的时候,发生了同步,所以在timer级别,这个是线程安全的,最后将task相关的参数赋值,主要包含nextExecutionTime(下一次执行时间),period(时间片),state(状态),然后将它放入queue队列中,做一次notify操作;我们看一下queue的结构:TaskQueue
1 class TaskQueue {
2
3 private TimerTask[] queue = new TimerTask[128];
4
5 private int size = 0;
可以看到,TaskQueue的结构是一个数组,加一个size,有点像ArrayList,ArrayList可以扩容,TaskQueue也可以,只是会造成内存拷贝,所以对于一个Timer来讲,只要内部的task个数不超过128是不会扩容的;内部 提供了add(TimerTask)、size()、getMin()、get(int)、removeMin()、quickRemove(int)、 rescheduleMin(long newTime)、isEmpty()、clear()、fixUp()、fixDown()、heapify()等,这里不再详述,有兴趣的朋友可以查阅相关资料。
4)、public void scheduleAtFixedRate(TimerTask task, Date firstTime,long period)
和3)的执行时间一样
需要注意,TimerTask 是以队列的方式一个一个被顺序运行的,所以执行的时间和预期的时间可能不一致,因为前面的任务可能消耗的时间较长,则后面的任务运行的时间会被延迟。延迟的任务具体开始的时间,就是依据前面任务的"结束时间"。Timer仍有很多需要注意的地方,也有一些其他的用法,不再赘述,有兴趣的伙伴可以继续查阅。