Java中的时间同步

3

在for循环中,我通过检索和处理车辆信息来控制基于仿真步骤的交通模拟器SUMO。为了确保我的程序以“实时”(1个仿真步骤=1秒)进行模拟,我希望在处理阶段之后使程序睡眠,直到下一个时间步骤开始。为了获得更好的结果,我基于最初获取的参考时间戳计算时间戳。

循环看起来像这样:

    System.out.println("start of traffic simulation ...");

    for (int i = 0; i < stepCount; i++)
    {
        System.out.println("step: " + i);

        // set before timeStamp
        beforeTimeStamp = System.currentTimeMillis();

        if (firstStep)
        {
            // get reference timeStamp
            referenceTimeStamp = beforeTimeStamp;
            firstStep = false;
        }
        else
        {
            // get next vehicleVector
            vehicleVector = masterControl.traCIclient.simulateStep();
        }

        // process vehicleVector

        // set after timeStamp
        afterTimeStamp = System.currentTimeMillis();

        processingTime = afterTimeStamp - beforeTimeStamp;

        // calculate sleepTime
        sleepTime = referenceTimeStamp + ((i + 1) * 1000) - afterTimeStamp;

       // sleep for sleepTime ms
       Thread.sleep(sleepTime);
    }

    System.out.println("end of traffic simulation ..."); 

以下是一些变量的输出结果:
步骤:0
beforeTimeStamp 1252317242565
参考时间:1252317242565
处理时间:394
测试时间:1252317243565
afterTimeStamp 1252317242959
sleepTime: 606
步骤:1 beforeTimeStamp 1252317242961 处理时间:665 测试时间:1252317244565 afterTimeStamp 1252317243626 sleepTime: 939(预期值:1000-665 = 335)
如您所见,睡眠时间仅对第一个模拟步骤正确。我不知道可能出了什么问题。有人有想法吗?
敬礼,
Markus
5个回答

10

由于Java没有关于线程调度的绝对保证,因此您无法使用标准Java精确睡眠一段时间。 Java受操作系统分配CPU时间的影响,并且您的程序将受到不可预测的垃圾收集暂停的影响。

如果您需要可预测的执行,则需要查看Java的实时规范这显然过于复杂了!

您可以使用java.util.concurrent包中的ScheduledExecutorService定期执行任务(通过休眠一段时间或以特定速率执行)。用法:

import static java.util.concurrent.*

Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(task, 0, 1, TimeUnit.SECONDS)

但这并不是完全准确的(请参阅JavaDoc):

请注意,相对延迟的过期时间可能与启用任务的当前日期不同,原因可能是网络时间同步协议、时钟漂移或其他因素

(强调我的)


1000个处理时间的问题是代码中的一个漏洞。我的答案是关于整个方法存在缺陷的解释,这是一个正确的答案,我不明白为什么它值得被投反对票。 - oxbow_lakes
我认为最紧急的问题是代码中的错误在哪里。然后跟进你不能可靠地做到这一点的问题是值得的。也许负评过于严厉了。已移除。 - Brian Agnew
@Markus - 由于执行器是单线程的,它不可能同时运行多个任务。我的(几乎肯定正确的)猜测是,在固定速率调度时,如果第一次调用花费的时间超过了周期,下一次调用将立即开始。 - oxbow_lakes
@oxbow_lakes 你知道ScheduledExecutorService是如何内部工作的吗?因为我正在尝试比较一些睡眠变量来完成我的硕士论文,这些信息对我非常有用... - Markus
你想了解什么?是关于Java代码还是sun.misc.Unsafe类?为什么不在Stack Overflow上提出另一个问题呢?那里肯定会有答案! - oxbow_lakes
显示剩余2条评论

4

为什么不睡眠1000 - 处理时间?这是你能得到最接近正确答案的方法。

你的解决方案只适用于第一步,因为它只对第一步正确。你假设每个步骤的处理都从参考时间 + (步骤 * 1000)开始,但你没有考虑开销(线程休眠、打印、垃圾回收)。 打印 参考时间戳 + ((i + 1) * 1000) - beforeTimeStamp,你就会明白我的意思。


+1 是因为我认为这是唯一解决代码中即时错误的答案。 - Brian Agnew
嗯,我不确定1000 - processingTime是否是我真正想要的,因为在这种情况下,睡眠时间仅基于上一步的信息。使用我的解决方案,我将能够避免一些时间漂移问题。但由于我无法影响执行开销,我可能别无选择,只能使用您的解决方案...对吧? - Markus
实际上最接近您想要的解决方案是其他评论中提到的scheduleAtFixedRate - 如果我正确理解了您的问题,其缺点是您无法确定一步完成后下一步开始(在不同线程上运行)- 如果这不是问题,那么ScheduledExecutorService将确保每个步骤在前一个步骤之后1000毫秒开始。 - laura

3

Java 5以后,有一个简单的标准解决方案。

请查看ScheduledExecutorService

它会像这样:

        ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
        service.scheduleAtFixedRate(new MyCode(), 0, 1, TimeUnit.SECONDS);

MyCode类实现了可运行接口,会每秒执行一次。

这并不是一个实时保证,但对您的情况应该足够。


2
正如其他人所强调的那样,你应该睡眠1000 - 处理时间。
尽管Java SE没有提供实时平台(因此您无法获得精确度),但我可能会看一下Timer类,特别是Timer.scheduleAtFixedRate(),它将负责定期安排任务。

0
你确定输出来自同一个程序吗?请查看内联突出显示的差异,
step:   0                                                                                                         
beforeTimeStamp 1252317242565                                                                                   
reference time: 1252317242565                                                                                   
processing time: 394                                                                                            
test time: 1252317243565                                                                                        
afterTimeStamp 1252317242959                                                                                    
sleepTime: 606 #### It didn't sleep this long at all
step: 1                                                                                                         
beforeTimeStamp 1252317242961 #### This is only 2ms from last afteTimeStamp                                                                                 
processing time: 665                                                                                            
test time: 1252317244565                                                                                        
afterTimeStamp 1252317243626                                                                                    
sleepTime: 939 (exspected: 1000 - 665 = 335)  #### This is to make up the deficit from last sleep                                                                                                

首个循环中有604(606-2)的睡眠时间不足。因此,第二个循环的正确睡眠时间应为939(335 + 604)。

Java的睡眠不是很准确,但不可能差这么多。我认为要么你在调试器中中断了程序,要么代码与输出不匹配。


哦,抱歉。我忘了提到,我目前并没有真正睡觉。因此,数值可能与您预期的不同。 - Markus

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