定时器计划与scheduleAtFixedRate有何区别?

11
public class MyTimerTask extends TimerTask{

    @Override
    public void run() {
        int i = 0; 
        try {
            Thread.sleep(100000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Run Me ~" + ++i);
        System.out.println("Test");

    }

}

Case 1 :-
    TimerTask task = new MyTimerTask();
    Timer timer = new Timer();
    timer.schedule(task, 1000,6000); // line 1
    System.out.println("End"); // here is bebug point. 

根据我理解 javadocs 中的描述,schedule() 方法应该满足每次执行都在前一个任务完成后进行调度的要求,因此我期望在第一行代码执行后会创建两个线程。

其中一个线程用于timer,它会为任务生成另一个线程。当第一个任务线程结束时,将创建另一个线程,以此类推。但是,在调试点上,我只看到与 Timer 相关的一个线程,而没有实现 Runnable 的任务线程,为什么会这样呢?

Case 2 :-

    TimerTask task = new MyTimerTask();
    Timer timer = new Timer();
    timer.scheduleAtFixedRate(task, 1000,6000); // line 1
    System.out.println("End"); // here is bebug point. 

根据我在JavaDocs中的理解,scheduleAtFixedRate()方法的期望是每次执行都相对于初始执行时间进行调度。在第一行之后应该创建大约17个线程(不必过于关注17,它可能比这多或少,但应该大于2)。

其中一个线程为timer,它应该会生成16个其他线程来对应每个任务的两个线程。首先,任务将休眠100秒,Timer应该会为下一个任务创建另一个线程,以此类推。但是在调试点上,我只看到了一个与Timer相关的线程。这里也可以看到任务的顺序执行。为什么没有17个线程呢?

更新:根据ScheduleAtFixedRate javadocs的说明,每个执行都是相对于初始执行时间进行调度的。如果由于任何原因(例如垃圾收集或其他后台活动)导致执行延迟,那么将会出现两个或更多次执行以“赶上”尚未完成的执行。那是什么意思?对我来说,这给了我这样的印象:如果第二个任务到期,即使第一个任务尚未完成,定时器也会为到期任务创建新线程。是不是这样?

4个回答

5

Timer使用Active Object模式,因此只有一个线程被使用,并且将新任务添加到线程的任务队列中。

计时器线程跟踪其队列中的所有任务并休眠,直到下一个任务被调度。然后,它会唤醒并直接通过调用task.run()执行任务本身,这意味着它不会生成另一个线程来执行代码。

这也意味着,如果您安排两个任务在同一时间执行,那么根据Active Object模式,它们将按顺序(一个接一个)在相同的控制线程上执行。这意味着第二个任务将在其预定时间之后执行(但可能不会太久)。

现在,为了明确回答您的问题,这里是Timer.class中从262-272行此处调度下一次运行任务的逻辑:

// set when the next task should be launched
if (task.fixedRate) {
    // task is scheduled at fixed rate
    task.when = task.when + task.period;
} else {
    // task is scheduled at fixed delay
    task.when = System.currentTimeMillis()
            + task.period;
}

// insert this task into queue
insertTask(task);

task.fixedRate的值为true,如果你使用timer.scheduleAtFixedRate()方法之一,则为false,如果你使用timer.schedule()方法之一。

task.when是任务被安排运行的“时间”(ticks)。

task.period是你传递给timer.schedule*()方法的间隔。

因此,从代码中我们可以看到,如果你使用固定速率,则重复任务将相对于其首次启动而安排运行。如果你不使用固定速率,则它将相对于上次运行时安排运行(相对于固定速率漂移,除非你的任务从未延迟且执行时间少于一个tick)。

这也意味着,如果任务落后,并且它在固定速率上,则Timer会保持重新安排任务以立即执行,直到它在给定的时间段内追上应该运行的总次数。

因此,如果你有一个任务,例如ping(),你计划以固定速率每10ms运行一次,并且在ping()方法中存在暂时阻塞以导致需要20ms才能执行,则Timer将立即再次调用ping(),并且它将不断这样做,直到达到给定的速率。


4

Timer的javadoc文档指出:

每个Timer对象都对应着一个后台线程,该线程被用来顺序地执行所有计时器任务。

基本上,它维护了一个任务队列,在您安排任务时将其添加到队列中。它使用一个线程遍历队列并执行任务。


但是scheduleAtFixedRate和schedule有什么区别呢?根据我的例子,它们应该表现得相似吗? - M Sach
2
@MSach 这在javadoc中有说明。schedule是固定延迟,而scheduleatFixedRate是固定速率。这会影响计划任务运行的时间。 - Sotirios Delimanolis
2
根据ScheduleAtFixedRate javadocs的说明,每次执行都相对于初始执行的计划执行时间进行调度。如果由于垃圾回收或其他后台活动而延迟执行,则会连续发生两个或多个执行以“赶上”。这是什么意思?对我来说,这给人的印象是,如果第二个任务到期,即使第一个任务尚未完成,定时器也会为到期任务创建新线程。是吗? - M Sach
@MSach,除 Timer 线程外,没有其他线程。任务在队列中。如果存在延迟,则在执行之间等待的时间将更短。 - Sotirios Delimanolis
1
@MSach,也就是说,在固定速率的情况下。在固定延迟的情况下,下一次执行将在延迟之后发生。 - Sotirios Delimanolis

1
定时器类会为每个实例创建一个线程,并且该线程会执行所有已安排的任务计划Timer#schedule或Timer#scheduleAtFixRate。
因此,正如您所观察到的,定时器只创建一个线程。
如果一个任务在前面的任务完成之前开始,那么后续的任务将等待前一任务完成后才开始。
因此,即使前一个任务没有完成并且下一个任务需要开始的时间已经到了,定时器也“从不”创建另一个线程。
因此,我建议您:
如果您想按时安排任务并执行任务,无论前面的任务是否已完成,请使用ScheduledThreadPoolExecutor而不是Timer。
即使您不想这样做,使用ScheduledThreadPoolExecutor比使用Timer更好,因为首先,由Timer计划的任务如果有一个任务抛出RuntimeException或Error,则永远不会完成。

1
日程安排如果开始时间已过,则不会执行错过的任务。 scheduleAtFixedRate如果开始时间已过,则会执行错过的任务。对于错过的任务,其开始时间将基于上一个任务的结束时间进行计算。当错过的任务完全执行后,新的正常任务的开始时间将基于上一个任务的开始时间进行计算。
BR Sanchez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接