1. 程式人生 > >java 多執行緒 Timer類

java 多執行緒 Timer類

Timer類      

        需要處理按計劃時間執行的任務時,可使用Timer類。
任務TimerTask
        建立一個Timer類的物件後,Timer物件的內部會啟動一個執行緒TimerThread,它只有一個執行緒來管理任務。
抽象類TimerTask實現了Runnable介面。 任務類繼承TimerTask類,重寫的run方法體就是要執行的任務。將任務物件提交給Timer物件,就可以執行執行緒了。


任務排程原理
              Timer檔案下有三個類,Timer類,Timer子類TimerThread繼承Thread類,是個執行緒,TaskQueue二叉堆,Timer類建立了TaskQueue類的queue物件,將其作為引數傳給TimerThread建立了thread物件
             所有加入Timer的任務都會通過sched方法放入到TaskQueue中(TaskQueue的add方法)【1】。(排程任務) TimerThread執行緒的run方法體中mainloop迴圈方法會不斷嘗試執行TaskQueue中的任務【2】,執行完所有任務【3】該執行緒才會結束。

【1】TaskQueue的add方法呼叫時,先判斷堆容量是否夠用,不夠就翻倍擴容,將任務放到最後,再呼叫fixUp對任務的優先順序排序,TimerTask按nextExecutionTime【4】進行堆排序。每次取堆中一個nextExecutionTime和當前系統時間進行比較,如果當前時間大於nextExecutionTime就會執行,如果是一次性任務,執行完了會將其從堆中移除。否則更新其nextExecutionTime的值。明顯地,nextExecutionTime最小的任務在queue[1],要被最先執行.
 

   【2】這裡僅分析下mainLoop()函式,原始碼附上:

  private void mainLoop() {
        while (true) {
            try {
                TimerTask task;
                boolean taskFired;
                synchronized(queue) {
                    // Wait for queue to become non-empty
                    while (queue.isEmpty() && newTasksMayBeScheduled)
                        queue.wait();
                    if (queue.isEmpty())
                        break; // Queue is empty and will forever remain; die
                    // Queue nonempty; look at first evt and do the right thing
                    long currentTime, executionTime;
                    task = queue.getMin();
                    synchronized(task.lock) {
                        if (task.state == TimerTask.CANCELLED) {
                            queue.removeMin();
                            continue;  // No action required, poll queue again
                        }
                        currentTime = System.currentTimeMillis();
                        executionTime = task.nextExecutionTime;
                        if (taskFired = (executionTime<=currentTime)) {
                            if (task.period == 0) { // Non-repeating, remove
                                queue.removeMin();
                                task.state = TimerTask.EXECUTED;
                            } else { // Repeating task, reschedule
                                queue.rescheduleMin(
                                  task.period<0 ? currentTime   - task.period
                                                : executionTime + task.period);
                            }
                        }
                    }
                    if (!taskFired) // Task hasn't yet fired; wait
                        queue.wait(executionTime - currentTime);
                }
                if (taskFired)  // Task fired; run it, holding no locks
                    task.run();
            } catch(InterruptedException e) {
            }
        }
    }

          首先判斷堆中任務為空且newTasksMayBeScheduled為true【6】,如果是,進入while迴圈體等待新任務到來,如果不是,取出第一個任務,判斷是否被取消,如被取消,從任務佇列移除該任務,進入下個排程週期,如未被取消,判斷是否到達執行時間,如未到,則等待差的這段時間,如果到了,先進行下次排程的準備工作(判斷period是否為0,如果為0,將這個任務從任務列表移除,如果不為0,就根據period>0或者period<0呼叫TaskQueue的rescheduleMin方法(重新設定nextExecution,再呼叫fixDown方法排序)),再執行run方法,即任務。

  【3】執行完所有任務的意思是,對單次的任務,執行完,對週期性重複的任務,取消它

【4】long nextExecutionTime 記錄該任務下次執行時間

【5】long period 描述任務的執行方式: 0表示只執行一次任務. 正數表示固定速率執行的任務. 從設定的開始時間起,每period時間執行一次。負數表示固定延遲執行的任務. 上次執行該任務的period時間後再次執行該任務。

【6】boolean  newTasksMayBeScheduled表示是否繼續等待新任務,預設設為true,通過檢視程式碼,發現當呼叫cancel方法或者殺死這個程序,沒有引用指向這個物件時,該變數被置為false


下面接著分析類方法:

Timer類

構造方法
Timer() 建立一個計時器物件。
Timer(boolean isDaemon) 建立一個計時器物件,設定其是否為守護執行緒。

Timer(String name) ,建立一個計時器物件,指定相關執行緒的名字。
Timer(Stringname, boolean isDaemon)
建立計時器物件,設定相關執行緒名字和是否為守護執行緒。
常用方法
void schedule(TimerTasktask,Date time)在指定時間執行某個任務。
void schedule(TimerTasktask,Date firstTime, long period)從指定的時間開始進行重複的以固定間隔時間執行某個任務。
void schedule(TimerTasktask, long delay)在指定延遲時間後執行某個任務。
void schedule(TimerTasktask, long delay, long period)從指定的延遲時間後開始進行重複地以固定間隔時間執行某個任務。
void scheduleAtFixedRate(TimerTasktask,Date firstTime, long period) 從指定時間開始,以固定頻率週期性地執行某個任務
void scheduleAtFixedRate(TimerTasktask, long delay, long period)從指定延時開始,以固定頻率週期性地執行某個任務。

void cancel() 取消所有已安排正在等待的任務,該定時器相關執行緒死亡。
int purge() 從當前計時器的任務佇列(task queue)中移除所有已取消的任務,將其引用置為null,方便jvm回收記憶體。對比TimerTask中 cancel方法,一次只能取消一個任務。
schedule和scheduleAtFixedRate的區別
如果設定的指定時間已經過去了才開始執行程式,schedule會將當前時間當做指定時間來開始週期性地執行任務,而scheduleAtFixedRate還是以設定的指定時間作為起始時間,先將因為遲到欠下的任務補上,再按照原來的設定接著執行。如果任務比較複雜,或者其他原因,如回收垃圾,大量執行緒在等待執行等因素導致延遲了某次的執行,schedule按固定時間間隔排程,每次的延遲傳播到後續的任務中,scheduleAtFixedRate按照固定比率排程,先執行多次將欠下的任務補回來,再按近似的固定頻率執行,長時間來看,scheduleAtFixedRate的執行頻率較穩定.


TimerTask類
Timer中的任務,在Timer物件中可安排一次執行或週期性執行的任務
public abstract class TimerTask  implements Runnable 抽象方法,繼承Runnable
構造方法:
protected TimerTask()  建立計時器任務物件。
常用方法:
boolean cancel() 取消此計時器任務。 一次只能取消一個
abstract void run()計時器任務,繼承TimerTask後要重寫該抽象方法。
long scheduledExecutionTime()返回該任務最近一次安排的執行時間。
 

使用示例:

每隔30s列印依次helloworld

package chen_chapter_9;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class TimerTest01 {
	public static void main(String[] args) {
		// 建立任務物件
		Work1 work1 = new Work1();
		// 建立定時器物件
		Timer timer = new Timer();

		// 建立SimpleDateFormat物件
		SimpleDateFormat sdf = new SimpleDateFormat();
		//設定日期時間的格式
		sdf.applyPattern("yyyy-MM-dd hh:mm:ss");
		// SimpleDateFormat物件的格式要和解析的格式一樣,否則報錯
		
		Date date = null;
		try {
			date = sdf.parse("2018-11-26 10:21:00");
			System.out.println(date);//列印時間
		} catch (ParseException e) {

			e.printStackTrace();
		}
		//安排任務
		timer.schedule(work1, date, 30000);

	}
}

class Work1 extends TimerTask {

	@Override
	public void run() {

		System.out.println("hello  world!");
	}

}
out:
Mon Nov 26 10:21:00 CST 2018
hello  world!
hello  world!
hello  world!

 

 

參考 https://blog.csdn.net/shengsummer/article/details/44460179

        https://www.cnblogs.com/xiaotaoqi/p/6874713.html