定時任務是軟件開發中經常遇到的問題。簡單的定時任務只需要在固定時間觸發它的執行就可以了。但是對於複雜的定時任務,可能是由多個任務組成一個任務組,它們之間存在依賴關係,一個任務執行的條件,必須是它的前置任務已經執行成功(或者沒有前置任務),它纔可以執行。例如下面這幅圖:
圖中任務的依賴關係爲:
任務1:依賴2,5
任務2:依賴3,4
任務3:無依賴
任務4:無依賴
任務5:無依賴
任務6:依賴2
這個任務關係圖其實就是“有向無環圖”(簡稱DAG)這種數據結構。圖是由一系列頂點和連接頂點的邊組成的數據結構。它分爲有向圖和無向圖。有向圖的邊是有方向的,即A->B這條邊和B->A是兩條不同的邊,而無向圖中,A->B和B->A是共用一條邊的。基於這種數據結構,我們可以用圖的頂點表示一個任務,而圖的邊表示任務之間的依賴關係,就可以基於有向無環圖來實現任務調度。
本文基於有向無環圖實現了一個簡單的任務調度系統的demo。
1.定義一個Executor接口
public interface Executor {
boolean execute();
}
這個接口代表一個可執行的任務,execute代表任務的執行。
2.定義一個Executor接口的實現Task
public class Task implements Executor{
private Long id;
private String name;
private int state;
public Task(Long id, String name, int state) {
this.id = id;
this.name = name;
this.state = state;
}
public boolean execute() {
System.out.println("Task id: [" + id + "], " + "task name: [" + name +"] is running");
state = 1;
return true;
}
public boolean hasExecuted() {
return state == 1;
}
}
id:任務id
name:任務名
state:任務狀態,簡化爲0:未執行,1:已執行
hasExecuted返回任務是否已執行。
3.任務圖
public class Digraph {
private Set<Task> tasks;
private Map<Task, Set<Task>> map;
public Digraph() {
this.tasks = new HashSet<Task>();
this.map = new HashMap<Task, Set<Task>>();
}
public void addEdge(Task task, Task prev) {
if (!tasks.contains(task) || !tasks.contains(prev)) {
throw new IllegalArgumentException();
}
Set<Task> prevs = map.get(task);
if (prevs == null) {
prevs = new HashSet<Task>();
map.put(task, prevs);
}
if (prevs.contains(prev)) {
throw new IllegalArgumentException();
}
prevs.add(prev);
}
public void addTask(Task task) {
if (tasks.contains(task)) {
throw new IllegalArgumentException();
}
tasks.add(task);
}
public void remove(Task task) {
if (!tasks.contains(task)) {
return;
}
if (map.containsKey(task)) {
map.remove(task);
}
for (Set<Task> set : map.values()) {
if (set.contains(task)) {
set.remove(task);
}
}
}
public Set<Task> getTasks() {
return tasks;
}
public void setTasks(Set<Task> tasks) {
this.tasks = tasks;
}
public Map<Task, Set<Task>> getMap() {
return map;
}
public void setMap(Map<Task, Set<Task>> map) {
this.map = map;
}
}
這個類使用了鄰接表來表示有向無環圖。tasks是頂點集合,也就是任務集合。map是任務依賴關係集合。key是一個任務,value是它的前置任務集合。一個任務執行的前提是它在map中沒有以它作爲key的entry,或者是它的前置任務集合中的任務都是已執行的狀態。
4.調度器
public class Scheduler {
public void schedule(Digraph digraph) {
while (true) {
List<Task> todo = new ArrayList<Task>();
for (Task task : digraph.getTasks()) {
if (!task.hasExecuted()) {
Set<Task> prevs = digraph.getMap().get(task);
if (prevs != null && !prevs.isEmpty()) {
boolean toAdd = true;
for (Task task1 : prevs) {
if (!task1.hasExecuted()) {
toAdd = false;
break;
}
}
if (toAdd) {
todo.add(task);
}
} else {
todo.add(task);
}
}
}
if (!todo.isEmpty()) {
for (Task task : todo) {
if (!task.execute()) {
throw new RuntimeException();
}
}
} else {
break;
}
}
}
public static void main(String[] args) {
Digraph digraph = new Digraph();
Task task1 = new Task(1L, "task1", 0);
Task task2 = new Task(2L, "task2", 0);
Task task3 = new Task(3L, "task3", 0);
Task task4 = new Task(4L, "task4", 0);
Task task5 = new Task(5L, "task5", 0);
Task task6 = new Task(6L, "task6", 0);
digraph.addTask(task1);
digraph.addTask(task2);
digraph.addTask(task3);
digraph.addTask(task4);
digraph.addTask(task5);
digraph.addTask(task6);
digraph.addEdge(task1, task2);
digraph.addEdge(task1, task5);
digraph.addEdge(task6, task2);
digraph.addEdge(task2, task3);
digraph.addEdge(task2, task4);
Scheduler scheduler = new Scheduler();
scheduler.schedule(digraph);
}
}
調度器的實現比較簡單,就是遍歷任務集合,找出待執行的任務集合,放到一個List中,再串行執行(若考慮性能,可優化爲並行執行)。若List爲空,說明所有任務都已執行,則這一次任務調度結束。
5.運行結果:
任務執行順序正確。
6.總結:
本文基於有向無環圖實現了一個任務調度的demo。可據此進行擴展,實現一個任務調度系統。本文的實現比較簡單,沒有考慮任務失敗的情形,可對此進行擴展。對任務的提交,存儲等,也可進行擴展。