1. 程式人生 > >Cron4j排程框架實現原理

Cron4j排程框架實現原理

       之前有篇部落格我們介紹了Cron4j改造與學習的內容 Cron4j排程框架學習與改造,這篇部落格我們從原始碼上看看Cron4j的實現機制。

示例:

public class TestMain {

	public static void main(String[] args) {
		
		// Creates a Scheduler instance.
		Scheduler s = new Scheduler();
		// Schedule a once-a-minute task.
		
		s.schedule("*/3 * * * * *", new Runnable() {
					int i = 0;
					public void run() {
						i++;
						System.out.println("Another minute ticked away..."+System.currentTimeMillis()/1000);
					}
				});
		// Starts the scheduler.
		s.start();
		// Will run for ten minutes.
		try {
			Thread.sleep(1000L * 60L * 10L);
		} catch (InterruptedException e) {
			;
		}
		// Stops the scheduler.
		s.stop();

	}

}

Scheduler呼叫schedule方法新增一個執行緒,在Scheduler掉頭start方法是會建立一個一直執行的執行緒TimerThread

public void start() throws IllegalStateException {
		synchronized (lock) {
			if (started) {
				throw new IllegalStateException("Scheduler already started");
			}
			// Initializes required lists.
			launchers = new ArrayList();
			executors = new ArrayList();
			// Starts the timer thread.
            //建立執行緒執行
			timer = new TimerThread(this);
			timer.setDaemon(daemon);
			timer.start();
			// Change the state of the scheduler.
			started = true;
		}
	}

在TimerThread執行緒run方法中會一直執行執行緒,每隔一秒會計算檢查cron表示式對應的定時任務規則,如果匹配則執行任務。

1、首先會獲取下一秒時間

2、計算當前時間與下一秒時間差

3、如果時間差超過1秒,則可能存在時間跳變情況,忽略重新計算

4、睡眠時間差時間

5、呼叫scheduler.spawnLauncher(millis);方法計算是否存在任務在這個時間點需要執行(每隔一秒計算一次)

public void run() {
		// What time is it?
		long millis = System.currentTimeMillis();
		// Work until the scheduler is started.
		//保持執行緒存貨
		for (;;) {
			// Calculating next seconds.
			//獲取當前時間的下一秒
			long nextSecond = ((System.currentTimeMillis() / 1000) + 1) * 1000;
			// Coffee break 'till next seconds comes!
			//獲取當前時間
			long sleepTime = (nextSecond - System.currentTimeMillis());
			//time is changed
			//如果存在時間跳變情況則直接忽略
			if(sleepTime > 1000) {
				continue;
			}
			//進行睡眠
			if (sleepTime > 0) {
				try {
					safeSleep(sleepTime);
				} catch (InterruptedException e) {
					// Must exit!
					break;
				}
			}
			millis = System.currentTimeMillis();
			// Launching the launching thread!
			//計算是否要進行任務執行
			scheduler.spawnLauncher(millis);
		}
		scheduler = null;
	}

在Scheduler的spawnLauncher中會新起一個執行緒LauncherThread去分別計算是否定時任務與當時時間匹配,如果匹配則執行

LauncherThread spawnLauncher(long referenceTimeInMillis) {
		TaskCollector[] nowCollectors;
		synchronized (collectors) {
			int size = collectors.size();
			nowCollectors = new TaskCollector[size];
			for (int i = 0; i < size; i++) {
				nowCollectors[i] = (TaskCollector) collectors.get(i);
			}
		}
		//新起任務去進行規制計算並執行
		LauncherThread l = new LauncherThread(this, nowCollectors,
				referenceTimeInMillis);
		synchronized (launchers) {
			launchers.add(l);
		}
		l.setDaemon(daemon);
		l.start();
		return l;
	}

在LauncherThread中會根據cron表示式與當前時間進行匹配,如果匹配成功則將相應的任務新增到執行緒池中去執行

public void run() {
		outer: for (int i = 0; i < collectors.length; i++) {
			TaskTable taskTable = collectors[i].getTasks();
			int size = taskTable.size();
			for (int j = 0; j < size; j++) {
				if (isInterrupted()) {
					break outer;
				}
				SchedulingPattern pattern = taskTable.getSchedulingPattern(j);
				//cron表示式與當前時間進行匹配
				if (pattern.match(scheduler.getTimeZone(), referenceTimeInMillis)) {
					Task task = taskTable.getTask(j);
					//任務新增到執行緒池中執行
					scheduler.spawnExecutor(task);
				}
			}
		}
		// Notifies completed.
		scheduler.notifyLauncherCompleted(this);
	}

任務新增到執行緒池中進行執行

TaskExecutor spawnExecutor(Task task) {
		TaskExecutor e = new TaskExecutor(this, task);
		synchronized (executors) {
			executors.add(e);
		}
		e.start(daemon);
		return e;
	}

總結:

cron4j目前支援最小的時間單位為分鐘,這樣線上程TimerThread中其實每分鐘才會對任務進行一次時間校驗並執行,當然也沒有對時間跳變進行處理,可能會導致時間跳變之後很長一段時間內任務不再執行。

改造:目前個人對cron4j進行改造,支援最小時間單位為妙,這樣TimerThread執行的頻率會變高,同樣對於時間跳變也進行了處理,當時間間隔超過1秒時則重新進行校驗處理。