Cron4j排程框架實現原理
阿新 • • 發佈:2018-12-14
之前有篇部落格我們介紹了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秒時則重新進行校驗處理。