2

我只有一个曾经在它自己的线程中执行此操作的程序:

public void run(){
    long lastTime = System.nanoTime();
    float lastSleep = 0;
    //Everything is in seconds.
    while(running){
        float delta = (System.nanoTime()-lastTime)/1000000000f;
        lastTime = System.nanoTime();
        manager.update(delta);
        panel.repaint();
        lastSleep = Math.max(maxTicSpeed-(delta-lastSleep),5/1000f);
        try{
            Thread.sleep((long) Math.round(lastSleep*1000));
        }catch(InterruptedException e){
            e.printStackTrace();
        }
    }
}

基本上,我一直被教导在这样循环时要睡觉,所以我做到了,我的程序至少休眠 5 毫秒,或者它可以在不超过限制的情况下休眠的最长时间(1/30 秒)。但是我在看书,睡眠听起来不太好。

http://msmvps.com/blogs/peterritchie/archive/2007/04/26/thread-sleep-is-a-sign-of-a-poorly-design-program.aspx

从他所说的听起来,如果我的程序太接近睡眠下限等,它甚至都不会睡觉。当 system.printing 及时更改时,更改的范围大约为 0.31508 - 0.03475,这对我来说真的足够好,因为我的程序会导致不准确。

话虽如此,我能做些什么呢?我正在考虑添加这样的东西而不是 try{Sleep}:

long waitTill = (long) (System.nanoTime()+lastTime/1000000000f), 
    now = System.nanoTime();
while(now < waitTill){
    now = System.nanoTime();
}

但是我的线程不会仍然占用相同数量的处理器时间吗?我认为关键是要阻止我们的线程占用比实际需要更多的处理器。

那么,我应该使用睡眠(具有更大的最小睡眠时间?),我应该使用我的替代方案,我应该使用另一种替代方案,还是应该让我的程序以无限制的速度循环?即使我考虑到睡眠不准确,我的编程是否也很糟糕?

谢谢您的帮助!

编辑: 所以,定时器已被推荐,但我知道如果我的任务在定时器再次调用之前没有完成,那么我会遇到问题。这是我的程序的一个明确问题。我觉得我已经通过使用增量处理了 Thread.sleep() 的问题,那么 Thread.sleep() 会像以前一样更好吗?

4

5 回答 5

5

为了解决这个问题,您需要重新考虑您的设计。本质上,您正在做的是定期安排的工作时间。Run/While/Sleep 模式有效,但正如您的研究发现的那样,它并不是最佳解决方案。

现代语言具有“任务运行”模式,允许编程环境/操作系统更好地管理任务的执行。

在 Java 中有java.util.timerjava.util.timertask。使用任务运行模式,您可以创建任务,并安排它以特定的时间间隔运行。

计时器还为您提供了一种更简洁的方法来停止执行循环,方法是取消计时器,而不是设置布尔标志。

来自评论:

有几个问题需要注意。如果您的任务的运行时间可能比计划的时间间隔长,则可以在前一个任务仍在执行时运行另一个任务。几个解决方案:

  1. 使用作业队列,对要完成的工作进行排队。如果队列中没有工作,则任务返回。
  2. 在 javascript 中常见的另一种方法是安排任务执行一次,并在任务结束时重新安排该任务执行一次。
  3. 一种不太优雅的方法,使用标志来指示正在执行的特定任务。这可行,但需要您正确管理标志的状态,这很容易出错。

另一个常见的问题是调度的计时器通常是尽最大努力实现的。也就是说,操作系统/框架尝试按计划运行任务,但不保证任务将在指定的时间间隔内准确执行。因此,如果您的任务需要严格的确定性调度,您可能需要更接近操作系统/硬件的解决方案。

于 2013-06-23T23:17:28.037 回答
1

你应该绝对避免纯粹的旋转(你的第二个例子)(除了一些特定的用例,比如同步原语),因为它消耗 CPU来做除了等待之外的事情,而且很容易导致死锁

这就是为什么您经常在睡眠中使用一些延迟来消耗更少的 CPU,并为其他线程提供更多工作机会,从而降低死锁的风险。

因此,只要您知道自己在做什么,您的第一个循环就没有问题。

但是您应该更喜欢专用于这些用例的组件:计时器

于 2013-06-23T23:17:42.440 回答
0

与其使用 sleep(),不如使用 wait() 和 notify()。

当使用 sleep() 时,线程退出运行队列并再次运行线程,操作系统需要做一些额外的工作,这纯粹是开销。

但是当线程等待时,它不会退出运行队列,因此没有开销。

于 2013-06-24T07:33:22.447 回答
0

正如 Alan 和 Pragmateek 所说,计时器通常比使用 Thread.sleep() 更受欢迎,然而,正如 Martin James 所说,“Sleep() 肯定会被滥用,而且经常被滥用,但这并不意味着它本质上是不正确的或某种形式的反模式”。

我的结论是,因为我使用了 delta,所以我处理了 Thread.sleep() 的不准确性。话虽如此,使用它没有问题。计时器会更难实现,因为我的任务需要在计时器再次调用之前完成,而我的程序很难保证这一点。

因此,在这个应用程序中,以及我使用它的方式中,Thread.sleep() 是一个完全可行的选择。

于 2013-06-24T01:15:47.337 回答
0

由于提出的问题是,如果计时器任务花费的时间比间隔时间长,那么计时器的行为方式。

我编写了一个愚蠢的示例程序来观察计时器的行为:

public class FlawedTimerTask extends TimerTask {
    final int taskId;
    final long sleepTime;
    int counter = 0;

    public FlawedTimerTask(int taskId, long sleepTime) {
        super();
        this.taskId = taskId;
        this.sleepTime = sleepTime;
    }

    @Override
    public void run() {
        long beginTimeInNs = System.nanoTime();
        System.out.println("taskId=" + taskId + ", run=" + counter + ", beginning at " + (beginTimeInNs-beginOfExperiment));
        try {
            Thread.sleep(sleepTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long endTimeInNs = System.nanoTime();
        System.out.println("taskId=" + taskId + ", run=" + counter + ", ending at " + (endTimeInNs-beginOfExperiment));
        counter++;
    }

    static long beginOfExperiment;

    public static void main(String[] args) {
        beginOfExperiment = System.nanoTime();
        Timer timer = new Timer();
        timer.schedule(new FlawedTimerTask(1, 800), 500, 500);
        timer.schedule(new FlawedTimerTask(2, 1000), 500, 2500);
    }
}

输出是:

taskId=1, run=0, beginning at 491988762
taskId=1, run=0, ending at 1291944877
taskId=2, run=0, beginning at 1292056514
taskId=2, run=0, ending at 2293928680
taskId=1, run=1, beginning at 2294036467
taskId=1, run=1, ending at 3094967160
taskId=1, run=2, beginning at 3095097404
taskId=1, run=2, ending at 3894902745
taskId=1, run=3, beginning at 3895045820
taskId=1, run=3, ending at 4695902088
taskId=2, run=1, beginning at 4696095849
taskId=2, run=1, ending at 5695887973
taskId=1, run=4, beginning at 5695991911
taskId=1, run=4, ending at 6496896941
taskId=1, run=5, beginning at 6497002803
taskId=1, run=5, ending at 7297814161
taskId=1, run=6, beginning at 7297998297
taskId=1, run=6, ending at 8098803239
taskId=2, run=2, beginning at 8098922575
taskId=2, run=2, ending at 9098814787
taskId=1, run=7, beginning at 9098971977
taskId=1, run=7, ending at 9899803866
taskId=1, run=8, beginning at 9899970038
taskId=1, run=8, ending at 10699807458
taskId=1, run=9, beginning at 10699912038
taskId=1, run=9, ending at 11500693882
taskId=2, run=3, beginning at 11500815143
taskId=2, run=3, ending at 12501656270
taskId=1, run=10, beginning at 12501781380
taskId=1, run=10, ending at 13302714640
taskId=1, run=11, beginning at 13302888511
taskId=1, run=11, ending at 14102727215
taskId=1, run=12, beginning at 14102929958
taskId=1, run=12, ending at 14903695762
taskId=2, run=4, beginning at 14903878616
taskId=2, run=4, ending at 15903607223
taskId=1, run=13, beginning at 15903775961
taskId=1, run=13, ending at 16705705613
taskId=1, run=14, beginning at 16705798644
taskId=1, run=14, ending at 17505650180
taskId=1, run=15, beginning at 17505881795
taskId=1, run=15, ending at 18306578307
taskId=2, run=5, beginning at 18306718815
taskId=2, run=5, ending at 19306666847
taskId=1, run=16, beginning at 19306757953
taskId=1, run=16, ending at 20107480129
taskId=1, run=17, beginning at 20107580217
taskId=1, run=17, ending at 20907534407
taskId=1, run=18, beginning at 20907640911
taskId=1, run=18, ending at 21709616117
taskId=2, run=6, beginning at 21709784855
taskId=2, run=6, ending at 22709563506
taskId=1, run=19, beginning at 22709664236
taskId=1, run=19, ending at 23510559642
taskId=1, run=20, beginning at 23510653956
taskId=1, run=20, ending at 24310465713
taskId=1, run=21, beginning at 24310572217
taskId=1, run=21, ending at 25111451583
taskId=2, run=7, beginning at 25111549105
taskId=2, run=7, ending at 26111453508
taskId=1, run=22, beginning at 26111544614
taskId=1, run=22, ending at 26913489022
taskId=1, run=23, beginning at 26913629531
taskId=1, run=23, ending at 27713421398
taskId=1, run=24, beginning at 27713577305
taskId=1, run=24, ending at 28514443839
taskId=2, run=8, beginning at 28514550985
taskId=2, run=8, ending at 29514349525
taskId=1, run=25, beginning at 29514496450
taskId=1, run=25, ending at 30315367475
taskId=1, run=26, beginning at 30315469488
taskId=1, run=26, ending at 31115349896
taskId=1, run=27, beginning at 31115475648
taskId=1, run=27, ending at 31917465609
taskId=2, run=9, beginning at 31917563773
taskId=2, run=9, ending at 32917368087
taskId=1, run=28, beginning at 32917524636
taskId=1, run=28, ending at 33718337276
taskId=1, run=29, beginning at 33718481634
taskId=1, run=29, ending at 34518366533
taskId=1, run=30, beginning at 34518459564
taskId=1, run=30, ending at 35319336363
taskId=2, run=10, beginning at 35319516009
taskId=2, run=10, ending at 36319338930
taskId=1, run=31, beginning at 36319440301
taskId=1, run=31, ending at 37121299378
taskId=1, run=32, beginning at 37121403957
taskId=1, run=32, ending at 37921223413
taskId=1, run=33, beginning at 37921324785
taskId=1, run=33, ending at 38722168863
taskId=2, run=11, beginning at 38722270877
taskId=2, run=11, ending at 39722259328

你可以观察到

  • 计时器在计时器任务的间隔(800ms,1000ms)中运行,而不是在其间隔 500ms
  • 定时器任务之间没有交错发生(在单线程定时器的实现中,主循环中有一个阻塞调用)
于 2013-06-24T13:28:06.197 回答