最近的一个小项目是做一个简单的数据仓库,需要将其他数据库的数据抽取出来,并通过而出抽取成页面需要的数据,以空间换时间的方式,让后端报表查询更快。
因为在抽取的过程中,有一定的先后顺序,需要做一个任务调度器,某一优先级的会先执行,然后会进入下一个优先级的队列任务中。
先定义了一个Map的集合,key是优先级,value是任务的集合,某一个优先级内的任务是并发执行的,而不同优先级是串行执行的,前一个优先级执行完之后,后面的才会执行。
ConcurrentHashMap<Integer/* 优先级. */, List<BaseTask>/* 任务集合. */> tasks = new ConcurrentHashMap<>();
这个调度管理有一个演进的过程,我先说第一个,这个是比较好理解的。
第一个版本:
首先对tasks集合中的key进行一个排序,我定义的是数字越小就有限执行,则进行遍历key值,并取出某个优先级的任务队列,执行任务队列的任务。任务的执行交给线程池去执行,在遍历内部,需要不断的检查这个队列中的任务是否都执行了,没有则一直等待否则进入到下个队列,任务执行的时候可能会抛出异常,但是不管任务是否异常,都将任务状态设置已执行。
下面是其核心代码:
public void run() {
//对key值进行排序
Enumeration<Integer> keys = tasks.keys();
List<Integer> prioritys = new ArrayList<>();
while (keys.hasMoreElements()) {
prioritys.add(keys.nextElement());
}
Collections.sort(prioritys);//升序
//对key进行遍历,执行某个某个优先级的任务队列
for (Integer priority : prioritys) {
List<BaseTask> taskList = tasks.get(priority);
if (taskList.isEmpty()) {
continue;
}
logger.info("execute priority {} task ", taskList.get(0).priority);
for (BaseTask task : taskList) {
executor.execute(() -> {
try {
task.doTask();
} catch (Exception e) {
e.printStackTrace();
}
});//线程中执行任务
}
while (true) {//等待所有线程都执行完成之后执行下一个任务队列
boolean finish = true;
for (BaseTask t : taskList) {
if (!t.finish) {
finish = false;
}
}
if (finish) {//当前任务都执行完毕
break;
}
Misc.sleep(1000);//Thread.sleep(1000)
}
Misc.sleep(1000);
}
}
关键代码很好理解,在任务执行之前,需要对所有任务都初始化,初始化的时候给出每个任务的优先级和任务名称,任务抽象类如下:
public abstract class BaseTask {
public String taskName;//任务名称
public Integer priority; //优先级
public boolean finish; //任务完成?
/**
* 执行的任务
*/
public abstract void doTask(Date date) throws Exception;
第一个版本的思路很简单。
第二个版本稍微有一点点复杂。这里主要介绍该版本的内容,后续将代码的链接附上。
程序是由SpringBoot搭建起来的,定时器是Sp