Java高效的调度结构?

时间:2011-09-09 14:08:28

标签: java algorithm scheduling

我为这个问题的长度道歉,但我认为包含足够的细节很重要,因为我正在寻找一个合适的方法解决我的问题,而不是一个简单的代码建议!


一般说明:

我正在开展一个项目,该项目要求能够在某个相对重复的间隔内“安排”任务。

这些间隔是根据某个内部时间来表示的,它表示为一个整数,随着程序的执行而递增(因此不等于实时)。每次发生这种情况时,计划将被插入以检查由于在此时间步执行而导致的任何任务。

如果执行任务,则应该将其重新安排为在相对于当前时间的位置再次运行(例如,以5个时间步长)。此相对位置只是存储为Task对象的整数属性。

问题:

我正在努力决定如何构建这个 - 部分是因为它是一组稍微难以寻找的搜索术语。

按照目前的情况,我想每次计时器递增时我需要:

  1. 执行日程安排中“0”位置的任务
  2. 在相对位置再次将这些任务重新添加到计划中(例如,每5个步骤重复一次的任务将返回到位置5)
  3. 时间表中的每组任务都会有'执行时间'减少一个(例如,位置1的任务将移动到位置0)
  4. 假设:

    有一些假设可能会限制我可以使用的解决方案:

    • 区间必须是相对的,而不是特定的时间,并且被定义为当前时间的整数步数
    • 这些间隔可以取任何整数值,例如没有界限
    • 多个任务可能会安排在同一时间步,但执行顺序并不重要
    • 所有执行应保留在单个线程中 - 由于其他限制,多线程解决方案合适

    我的主要问题是:

    我如何设计此计划以有效的方式工作?哪些数据类型/集合可能有用?

    我应该考虑其他结构/方法吗?

    我错误地解雇了调度框架(例如Quartz),它似乎在“真实”时域而非“非真实”时域中工作更多?


    非常感谢任何可能的帮助。如有需要,请随时评论以获取更多信息,我将在任何需要的地方进行编辑!

7 个答案:

答案 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使用实时工作,因此可以使用ConditionLockSupport中提供的可变定时等待方法。您可以实现像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