【測試詳解】關於java定時器的常見問題,例如無法取消,被延期執行等

定時器Timer

  • Timer timer = new Timer(true);將定時器設置爲守護線程(daemon),即當用戶線程都已經執行完畢退出以後,jvm就會結束守護進程,不管守護進程是否還有任務,程序退出。

定時器任務TimerTask

  • 可以由計時器進行一次性或重複執行的任務,簡單來說就是計時器要執行的任務。

ok,把兩者聯繫起來使用就是:

timer.schedule(TimerTask task, long delay, long period)在指定 的延遲之後開始 ,重新執行 固定延遲執行的指定任務。

還有更多API也順便在這裏copy出來,看的時候方便:

    • void cancel()

      終止此計時器,丟棄任何當前計劃的任務。

      int purge()

      從該計時器的任務隊列中刪除所有取消的任務。

      void schedule(TimerTask task, Date time)

      在指定的時間安排指定的任務執行。

      void schedule(TimerTask task, Date firstTime, long period)

      從指定 的時間開始 ,對指定的任務執行重複的 固定延遲執行

      void schedule(TimerTask task, long delay)

      在指定的延遲之後安排指定的任務執行。

      void schedule(TimerTask task, long delay, long period)

      在指定 的延遲之後開始 ,重新執行 固定延遲執行的指定任務。

      void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)

      從指定的時間 開始 ,對指定的任務執行重複的 固定速率執行

      void scheduleAtFixedRate(TimerTask task, long delay, long period)

      在指定的延遲之後 開始 ,重新執行 固定速率的指定任務。

其實timer相當於一個後臺單線程,timerTask就是timer裏面的單個線程任務,timer除非設置成守護線程或則主動使用cancle方法終止計時器,否則會一直執行,而其中獨立的timerTask也可以單獨調用timerTask.cancle()方法終止任務,但是此方法只是終止了timer其中的某個獨立的任務,timer仍然會繼續執行:

例如:

Timer timer = new Timer();

TimerTask tt1 = new TimerTask(){public void run(){//執行代碼}};

TimerTask tt2 = new TimerTask(){public void run(){//執行代碼}};

timer.schedule(tt1,new Date(),5000);

timer.schedule(tt2,1000,5000);

調用其中的tt1的cancle方法只會終止tt1的任務,而timer和tt2任然會繼續執行。

【附加說明】串行化特性:

       這樣就是把tt1和tt2兩個任務放入了timer線程當中串行執行,即tt1執行完才輪到tt2執行,t可以看到tt1和tt2其實應該是同時執行的,但是tt2延遲一秒鐘期間,如果tt1還未執行完畢,那麼tt2的開始執行時間因而會被推遲到tt1執行完畢後纔開始執行,這就是爲什麼有時候發現爲什麼沒有按時執行,可以從這裏分析一下問題原因

 

----timer單線程

  • 對應於每個Timer對象是單個後臺線程,用於依次執行所有定時器的所有任務。

public class TestTImerCircle2 {
	public static void main(String[] args) throws InterruptedException {
		Timer t = new Timer();
		//第一個任務
		TimerTask tt1 = new TimerTask() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("running1");
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("running1 end");
			}
		};
		//第二個任務
		TimerTask tt2 = new TimerTask() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("running2");
				try {
					Thread.sleep(10000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("running2 end");
			}
		};
		t.schedule(tt2, new Date());
		t.schedule(tt1, new Date());
		
	}
}

 可以看到輸出:

running2
running2 end
running1
running1 end

程序未終止

 running2 end執行在running1之前,如果是多線程並行running1會在running2 end之前,此處證明timer是單個線程後臺。

需要區別與多線程並行的線程池執行任務:

​
public class ThreadPoolTest{
	public static void main(String[] args) throws InterruptedException {
		ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
		//第一個任務
		Runnable tt1 = new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("running1");
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("running1 end");
			}
		};
		//第二個任務
		Runnable tt2 = new Runnable() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("running2");
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("running2 end");
			}
		};
		newCachedThreadPool.submit(tt1);
		newCachedThreadPool.submit(tt2);
		newCachedThreadPool.shutdown();
	}
}

​

 如上,tt1和tt2是同時進行的,輸出結果是:

running2
running1
running1 end
running2 end

程序終止

----timer的終止

  • 空引用回收:在最後一次對Timer對象的引用後所有未完成的任務已完成執行,定時器的任務執行線程正常終止(並被收集到垃圾回收)。 但是,這可能需要任意長時間的發生。

  • 主動終止計時器:如果主叫方想要快速終止定時器的任務執行線程,則調用者應該調用定時器的cancel方法。


public class TestTImerCircle {
	public static void main(String[] args) {
		Timer t = new Timer();
		TimerTask tt = new TimerTask() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("running");
			}
		};
		t.schedule(tt, new Date());
        //方法一:置空等待回收
		t = null;
        //方法二:cancle終止
        //t.cancle();
		System.gc();
	}
}

執行結果: 

running

程序終止

以上,如果註釋掉最後一行代碼,發現很久(非常久,測試幾個鍾結束)都沒有反應,因爲jvm回收垃圾發生在任何時候,而主動調用gc回收發現程序立馬終止,驗證以上結論1。註釋掉t=null,取消註釋t.cancle()發現running並沒有輸出而是直接被終止,這是因爲任務還沒開始執行就被終止了,使用以下代碼可以看到輸出running:

public class TestTImerCircle2 {
	public static void main(String[] args) throws InterruptedException {
		Timer t = new Timer();
		TimerTask tt = new TimerTask() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("running");
			}
		};
		t.schedule(tt, new Date());
		Thread.sleep(100);
		t.cancel();
		
//		t = null;
//		System.gc();
		
	}
}

執行結果: 

running

程序終止

最重要的一種情況需要注意,如果任務正在執行,還未執行完,那麼cancle()將不會引起正在進行的任務立馬結束,而是等待當前任務完成以後,才終止計時器

public class TestTImerCircle2 {
	public static void main(String[] args) throws InterruptedException {
		Timer t = new Timer();
		TimerTask tt = new TimerTask() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("running");
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("running end");
			}
		};
		t.schedule(tt, new Date());
		Thread.sleep(100);
		t.cancel();
		
//		t = null;
//		System.gc();
		
	}
}

執行結果: 

running

等待2秒鐘

running end

程序終止

可以看到,兩秒後才輸出running end,另外cancle一旦觸發,不管是否設置了重複執行,只要當前沒有正在執行中的任務,立刻終止計時器: 

public class TestTImerCircle2 {
	public static void main(String[] args) throws InterruptedException {
		Timer t = new Timer();
		TimerTask tt = new TimerTask() {
			@Override
			public void run() {
				// TODO Auto-generated method stub
				System.out.println("running");
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("running end");
			}
		};
		t.schedule(tt, new Date(),1);
		Thread.sleep(100);
		t.cancel();
		
//		t = null;
//		System.gc();
		
	}
}

執行結果:

running

執行2秒鐘後

running end

程序終止

以上設置了隔1毫秒執行一次,但是執行完第一次後程序立刻被結束退出了,此處證明cancle具有延遲生效特性(有正在執行的任務)和必定執行特性(代碼一旦被執行,必定在下一次任務前終止計時器)。

 

以上就介紹這麼多,timer和timertask的使用應該足夠了,然後還有一個是purge這個方法,從該計時器的任務隊列中刪除所有取消的任務,返回刪除數,不知道爲什麼測試總是0,後續有空再更新測試吧。

 

 

對了,以上例子因爲使用了timer.schedule(TimerTask timerTask,Date time)這個方法,在指定的時間安排指定的任務執行,並且只執行一次,所以如果碰到需要使用timer.schedule(TimerTask timerTask,Date time,long period)這種方法來重複間隔執行任務的情況,而達到條件要取消掉其中的一個timertask,那就可以在timertask的實現方法run中加入this.cancle()即可把timer中的該任務取消掉了,接着應該就可以使用timer.purge()這個方法清除掉,也可以不使用,等待jvm回收即可。

TimerTask tt2 = new TimerTask() {
			@Override
			public void run() {
				// 達到條件,結束
				this.cancle();
			}
		};

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章