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     }
View Code

  周期执行任务,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仍有很多需要注意的地方,也有一些其他的用法,不再赘述,有兴趣的伙伴可以继续查阅。

  参考来源:https://www.cnblogs.com/0201zcr/p/4703061.html

posted @ 2019-07-26 15:42  胜金  阅读(1875)  评论(0编辑  收藏  举报