定時計划任務功能在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仍有很多需要注意的地方,也有一些其他的用法,不再贅述,有興趣的伙伴可以繼續查閱。