我为这个问题的长度道歉,但我认为包含足够的细节很重要,因为我正在寻找一个合适的方法解决我的问题,而不是一个简单的代码建议!
我正在开展一个项目,该项目要求能够在某个相对重复的间隔内“安排”任务。
这些间隔是根据某个内部时间来表示的,它表示为一个整数,随着程序的执行而递增(因此不等于实时)。每次发生这种情况时,计划将被插入以检查由于在此时间步执行而导致的任何任务。
如果执行任务,则应该将其重新安排为在相对于当前时间的位置再次运行(例如,以5个时间步长)。此相对位置只是存储为Task对象的整数属性。
我正在努力决定如何构建这个 - 部分是因为它是一组稍微难以寻找的搜索术语。
按照目前的情况,我想每次计时器递增时我需要:
有一些假设可能会限制我可以使用的解决方案:
我的主要问题是:
我如何设计此计划以有效的方式工作?哪些数据类型/集合可能有用?
我应该考虑其他结构/方法吗?
我错误地解雇了调度框架(例如Quartz),它似乎在“真实”时域而非“非真实”时域中工作更多?
非常感谢任何可能的帮助。如有需要,请随时评论以获取更多信息,我将在任何需要的地方进行编辑!
答案 0 :(得分:2)
嗯, Quartz 是非常强大的工具,但它的配置可能性有限,所以如果你需要特定的功能,你应该自己编写自己的解决方案。
但是,研究 Quartz 源代码和数据结构是一个好主意,因为它们已成功处理了您会发现的许多问题。数据库级别的进程间同步,运行延迟的任务等。
我曾经写过一个我自己的调度程序,它适用于那些可靠的Quartz不易调整的任务,但是一旦我学会了Quartz,我就知道我可以在我的解决方案中有多少改进,知道它是怎么回事在Quartz完成。
答案 1 :(得分:1)
循环链表可能是您正在寻找的数据结构。您只需递增循环任务列表中“当前”字段的索引,而不是递减每个任务元素中的字段。伪代码结构可能如下所示:
tick():
current = current.next()
for task : current.tasklist():
task.execute()
每当您安排新任务时,只需将其添加到当前'tick'前面的N位置
答案 2 :(得分:1)
这个怎么样,它使用你自己的Ticks和executeNextInterval():
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public class Scheduler {
private LinkedList<Interval> intervals = new LinkedList<Scheduler.Interval>();
public void addTask(Runnable task, int position) {
if(position<0){
throw new IllegalArgumentException();
}
while(intervals.size() <= position){
intervals.add(new Interval());
}
Interval interval = intervals.get(position);
interval.add(task);
}
public void executeNextInterval(){
Interval current = intervals.removeFirst();
current.run();
}
private static class Interval {
private List<Runnable> tasks = new ArrayList<Runnable>();
public void add(Runnable task) {
tasks.add(task);
}
public void run() {
for (Runnable task : tasks) {
task.run();
}
}
}
}
您可能希望添加一些错误处理,但它应该可以完成您的工作。
这里有一些UnitTests:)
import junit.framework.Assert;
import org.junit.Test;
public class TestScheduler {
private static class Task implements Runnable {
public boolean didRun = false;
public void run() {
didRun = true;
}
}
Runnable fail = new Runnable() {
@Override
public void run() {
Assert.fail();
}
};
@Test
public void queue() {
Scheduler scheduler = new Scheduler();
Task task = new Task();
scheduler.addTask(task, 0);
scheduler.addTask(fail, 1);
Assert.assertFalse(task.didRun);
scheduler.executeNextInterval();
Assert.assertTrue(task.didRun);
}
@Test
public void queueWithGaps() {
Scheduler scheduler = new Scheduler();
scheduler.addTask(fail, 1);
scheduler.executeNextInterval();
}
@Test
public void queueLonger() {
Scheduler scheduler = new Scheduler();
Task task0 = new Task();
scheduler.addTask(task0, 1);
Task task1 = new Task();
scheduler.addTask(task1, 1);
scheduler.addTask(fail, 2);
scheduler.executeNextInterval();
scheduler.executeNextInterval();
Assert.assertTrue(task0.didRun);
Assert.assertTrue(task1.didRun);
}
}
答案 3 :(得分:1)
以下是一些想法:
保持一切简单。如果您没有数百万个任务,则无需优化数据结构(除了骄傲或过早优化的冲动)。
避免相对时间。使用绝对内部刻度。如果添加任务,请将“下次运行”设置为当前刻度值。将其添加到列表中,按时间对列表进行排序。
在寻找任务时,从列表的头部开始,选择有时间&lt; =当前刻度的所有内容,运行任务。
将所有这些任务收集到另一个列表中。毕竟运行后,根据当前的勾选和增量计算“下次运行”(这样你就不会得到循环的任务),将所有这些都添加到列表中,排序。
答案 4 :(得分:1)
查看DelayQueue
使用PriorityQueue
维护此类有序事件列表的方式。 DelayQueue
使用实时工作,因此可以使用Condition
和LockSupport
中提供的可变定时等待方法。您可以实现像SyntheticDelayQueue
这样的行为,其行为方式与DelayQueue
相同,但使用您自己的合成时间服务。你显然必须用jdk替换免费提供的定时等待/信令机制,这可能是非常有效的。
答案 5 :(得分:1)
如果必须这样做,我会创建一个简单的队列(链表变体)。此队列将包含动态数据结构(例如,简单列表),其中包含需要完成的所有任务。在每个时间间隔(或时间步长),进程读取队列的第一个节点,执行它在该节点列表中找到的指令。在每次执行结束时,它将计算重新安排并将新执行添加到队列中的另一个节点,或者在将指令存储在该节点内之前创建直到该位置的节点。然后移除第一个节点,并在下一个时间步骤执行第二个节点(现在是第一个节点)。该系统也不需要跟踪任何整数,并且所有需要的数据结构都可以在java语言中找到。这应该可以解决你的问题。
答案 6 :(得分:0)
使用ScheduledExecutorService。它拥有您需要的所有内容。这里使用起来非常简单:
// Create a single-threaded ScheduledExecutorService
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); // 1 thread
// Schedule something to run in 10 seconds time
scheduler.schedule(new Runnable() {
public void run() {
// Do something
}}, 10, TimeUnit.SECONDS);
// Schedule something to run in 2 hours time
scheduler.schedule(new Runnable() {
public void run() {
// Do something else
}}, 2, TimeUnit.HOURS);
// etc as you need