Java多線程定時器Timer原理及實現
前言
定時/計劃功能在Java應用的各個領域都使用得非常多,比方說Web層面,可能一個項目要定時采集話單、定時更新某些緩存、定時清理一批不活躍用戶等等。定時計劃任務功能在Java中主要使用的就是Timer對象,它在內部使用多線程方式進行處理,所以它和多線程技術關聯還是相當大的。那和ThreadLocal一樣,還是先講原理再講使用,Timer的實現原理不難,就簡單掃一下就好了。
Timer的schedule(TimeTask task, Date time)的使用
該方法的作用是在執(zhí)行的日期執(zhí)行一次任務
1、執(zhí)行任務的時間晚于當前時間:未來執(zhí)行
private static Timer timer = new Timer();
static public class MyTask extends TimerTask
{
public void run()
{
System.out.println("運行了!時間為:" + new Date());
}
}
public static void main(String[] args) throws Exception
{
MyTask task = new MyTask();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2015-10-6 12:14:00";
Date dateRef = sdf.parse(dateString);
System.out.println("字符串時間:" + dateRef.toLocaleString() + " 當前時間:" + new Date().toLocaleString());
timer.schedule(task, dateRef);
}
看一下運行效果:
字符串時間:2015-10-6 12:14:00 當前時間:2015-10-6 12:13:23 運行了!時間為:Tue Oct 06 12:14:00 CST 2015
執(zhí)行時間和但前時間不一致,而是和dateRef的時間一直,證明了未來執(zhí)行。任務雖然執(zhí)行完了,但進程沒有銷毀,控制臺上的方框可以看到還是紅色的,看下Timer的源代碼:
public Timer() {
this("Timer-" + serialNumber());
}
public Timer(String name) {
thread.setName(name);
thread.start();
}
所以,啟動一個Timer就是啟動一個新線程,但是這個新線程并不是守護線程,所以它會一直運行。要運行完就讓進程停止的話,設置Timer為守護線程就好了,有專門的構造函數可以設置:
public Timer(boolean isDaemon) {
this("Timer-" + serialNumber(), isDaemon);
}
public Timer(String name, boolean isDaemon) {
thread.setName(name);
thread.setDaemon(isDaemon);
thread.start();
}
2、計劃時間早于當前時間:立即執(zhí)行
如果執(zhí)行任務的時間早于當前時間,那么立即執(zhí)行task的任務:
private static Timer timer = new Timer();
static public class MyTask extends TimerTask
{
public void run()
{
System.out.println("運行了!時間為:" + new Date());
}
}
public static void main(String[] args) throws Exception
{
MyTask task = new MyTask();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2014-10-6 12:14:00";
Date dateRef = sdf.parse(dateString);
System.out.println("字符串時間:" + dateRef.toLocaleString() + " 當前時間:" + new Date().toLocaleString());
timer.schedule(task, dateRef);
}
看一下運行效果:
字符串時間:2014-10-6 12:14:00 當前時間:2015-10-6 12:20:10 運行了!時間為:Tue Oct 06 12:20:10 CST 2015
執(zhí)行時間和當前時間一致,證明了立即執(zhí)行
3、多個TimerTask任務執(zhí)行
Timer中允許有多個任務:
private static Timer timer = new Timer();
static public class MyTask extends TimerTask
{
public void run()
{
System.out.println("運行了!時間為:" + new Date());
}
}
public static void main(String[] args) throws Exception
{
MyTask task1 = new MyTask();
MyTask task2 = new MyTask();
SimpleDateFormat sdf1 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
SimpleDateFormat sdf2 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString1 = "2015-10-6 12:26:00";
String dateString2 = "2015-10-6 12:27:00";
Date dateRef1 = sdf1.parse(dateString1);
Date dateRef2 = sdf2.parse(dateString2);
System.out.println("字符串時間:" + dateRef1.toLocaleString() + " 當前時間:" + new Date().toLocaleString());
System.out.println("字符串時間:" + dateRef2.toLocaleString() + " 當前時間:" + new Date().toLocaleString());
timer.schedule(task1, dateRef1);
timer.schedule(task2, dateRef2);
}
看一下運行結果:
字符串時間:2015-10-612:26:00當前時間:2015-10-612:25:38
字符串時間:2015-10-612:27:00當前時間:2015-10-612:25:38
運行了!時間為:TueOct0612:26:00CST2015
運行了!時間為:TueOct0612:27:00CST2015
可以看到,運行時間和設置的時間一致,證明了未來可以執(zhí)行多個任務。另外注意,Task是以隊列的方式一個一個被順序執(zhí)行的,所以執(zhí)行的時間有可能和預期的時間不一致,因為前面的任務可能消耗過長,后面任務的運行時間也有可能被延遲。
代碼就不寫了,舉個例子,任務1計劃12:00:00被執(zhí)行,任務2計劃12:00:10被執(zhí)行,結果任務1執(zhí)行了30秒,那么任務2將在12:00:30被執(zhí)行,因為Task是被放入隊列中的,因此必須一個一個順序運行。
Timer的schedule(TimerTasktask,DatefirstTime,longperiod)
該方法的作用是在指定的日期之后,按指定的間隔周期性地無限循環(huán)地執(zhí)行某一人物
1、計劃時間晚于當前時間:未來執(zhí)行
static public class MyTask extends TimerTask
{
public void run()
{
System.out.println("運行了!時間為:" + new Date());
}
}
public static void main(String[] args) throws Exception
{
MyTask task = new MyTask();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2015-10-6 18:00:00";
Timer timer = new Timer();
Date dateRef = sdf.parse(dateString);
System.out.println("字符串時間:" + dateRef.toLocaleString() + " 當前時間:" + new Date().toLocaleString());
timer.schedule(task, dateRef, 4000);
}
看一下運行結果:
字符串時間:2015-10-6 18:01:00 當前時間:2015-10-6 18:00:15
運行了!時間為:Tue Oct 06 18:01:00 CST 2015
運行了!時間為:Tue Oct 06 18:01:04 CST 2015
運行了!時間為:Tue Oct 06 18:01:08 CST 2015
運行了!時間為:Tue Oct 06 18:01:12 CST 2015
...
看到從設定的時間開始,每隔4秒打印一次,無限打印下去
2、計劃時間早于當前時間:立即執(zhí)行
static public class MyTask extends TimerTask
{
public void run()
{
System.out.println("運行了!時間為:" + new Date());
}
}
public static void main(String[] args) throws Exception
{
MyTask task = new MyTask();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2014-10-6 18:01:00";
Timer timer = new Timer();
Date dateRef = sdf.parse(dateString);
System.out.println("字符串時間:" + dateRef.toLocaleString() + " 當前時間:" + new Date().toLocaleString());
timer.schedule(task, dateRef, 4000);
}
看一下運行結果:
字符串時間:2014-10-6 18:01:00 當前時間:2015-10-6 18:02:46
運行了!時間為:Tue Oct 06 18:02:46 CST 2015
運行了!時間為:Tue Oct 06 18:02:50 CST 2015
運行了!時間為:Tue Oct 06 18:02:54 CST 2015
運行了!時間為:Tue Oct 06 18:02:58 CST 2015
運行了!時間為:Tue Oct 06 18:03:02 CST 2015
...
看到運行時間比當前時間早,從當前時間開始,每隔4秒打印一次,無限循環(huán)下去
TimerTask的cancel()方法
TimerTask的cancel()方法的作用是將自身從任務隊列中清除:
static public class MyTaskA extends TimerTask
{
public void run()
{
System.out.println("A運行了!時間為:" + new Date());
this.cancel();
}
}
static public class MyTaskB extends TimerTask
{
public void run()
{
System.out.println("B運行了!時間為:" + new Date());
}
}
public static void main(String[] args) throws Exception
{
MyTaskA taskA = new MyTaskA();
MyTaskB taskB = new MyTaskB();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2015-10-6 18:10:00";
Timer timer = new Timer();
Date dateRef = sdf.parse(dateString);
System.out.println("字符串時間:" + dateRef.toLocaleString() + " 當前時間:" + new Date().toLocaleString());
timer.schedule(taskA, dateRef, 4000);
timer.schedule(taskB, dateRef, 4000);
}
看一下運行結果:
字符串時間:2015-10-6 18:10:00 當前時間:2015-10-6 18:09:47 A運行了!時間為:Tue Oct 06 18:10:00 CST 2015 B運行了!時間為:Tue Oct 06 18:10:00 CST 2015 B運行了!時間為:Tue Oct 06 18:10:04 CST 2015 B運行了!時間為:Tue Oct 06 18:10:08 CST 2015 B運行了!時間為:Tue Oct 06 18:10:12 CST 2015 ...
看到TimeTask的cancel()方法是將自身從任務隊列中被移除,其他任務不受影響
Timer的cancel()方法
把上面代碼改動一下:
private static Timer timer = new Timer();
static public class MyTaskA extends TimerTask
{
public void run()
{
System.out.println("A運行了!時間為:" + new Date());
timer.cancel();
}
}
static public class MyTaskB extends TimerTask
{
public void run()
{
System.out.println("B運行了!時間為:" + new Date());
}
}
public static void main(String[] args) throws Exception
{
MyTaskA taskA = new MyTaskA();
MyTaskB taskB = new MyTaskB();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateString = "2015-10-6 18:10:00";
Date dateRef = sdf.parse(dateString);
System.out.println("字符串時間:" + dateRef.toLocaleString() + " 當前時間:" + new Date().toLocaleString());
timer.schedule(taskA, dateRef, 4000);
timer.schedule(taskB, dateRef, 4000);
}
看一下運行結果:
字符串時間:2015-10-618:10:00當前時間:2015-10-618:14:15
A運行了!時間為:TueOct0618:14:15CST2015
全部任務都被清除,并且進程被銷毀。不過注意一下,cancel()方法未必一定會停止執(zhí)行計劃任務,可能正常執(zhí)行,因為cancel()方法會嘗試去獲取queue鎖,如果并沒有獲取到queue鎖的話,TimerTask類中的任務繼續(xù)執(zhí)行也是完全有可能的
其他方法
再列舉一些Timer中的其他schedule的重載方法的作用,就不提供證明的代碼了,可以自己嘗試一下:
1、schedule(TimerTasktask,longdelay)
以當前時間為參考,在此時間基礎上延遲指定的毫秒數后執(zhí)行一次TimerTask任務
2、schedule(TimerTasktask,longdelay,longperiod)
以當前時間為參考,在此時間基礎上延遲指定的毫秒數后,以period為循環(huán)周期,循環(huán)執(zhí)行TimerTask任務
3、scheduleAtFixedRate(TimerTasktask,DatefirstTime,longperiod)
在延時的場景下,schedule方法和scheduleAtFixedRate方法沒有區(qū)別,它們的區(qū)別只是在非延時上。如果執(zhí)行任務的時間沒有被延時,對于schedule方法來說,下一次任務執(zhí)行的時間參考的是上一次任務的開始時間來計算的;對于scheduleAtFixedRate方法來說,下一次任務執(zhí)行的時間參考的是上一次任務的結束時間來計算的
總結
以上就是本文關于Java多線程定時器Timer原理及實現的全部內容,希望對大家有所幫助。感興趣的朋友可以繼續(xù)參閱本站:
Java多線程之readwritelock讀寫分離的實現代碼
如有不足之處,歡迎留言指出。
相關文章
Java操作minio刪除文件夾及其文件方法(MinIO基本使用)
MinIO是一個高性能、無限擴展的開源對象存儲服務器,它以對象的形式存儲數據,并兼容Amazon S3接口,它適用于大規(guī)模數據存儲、大數據分析、文件共享和備份等應用場景,這篇文章主要介紹了java操作minio刪除文件夾及其文件方法,需要的朋友可以參考下2024-02-02
Java8新特性之lambda的作用_動力節(jié)點Java學院整理
我們期待了很久lambda為java帶來閉包的概念,但是如果我們不在集合中使用它的話,就損失了很大價值。現有接口遷移成為lambda風格的問題已經通過default methods解決了,在這篇文章將深入解析Java集合里面的批量數據操作解開lambda最強作用的神秘面紗。2017-06-06

