Java“scheduleAtFixedRate”的替代方案是什么?

10

我有一个Java应用程序,用于通过UART连接(RS422)与嵌入式设备通信。主机每5毫秒查询微控制器获取数据。以前,我一直使用ScheduledExecutorService scheduleAtFixedRate来调用我的通信协议方法,但这种方法对于所需的精度非常不可靠(正如其他帖子所述)。从微控制器返回的数据中包括一个时间戳(以微秒为单位),使我可以独立于JVM验证接收到的数据包之间的间隔。需要指出的是,当使用scheduleAtFixedRate时,间隔变化非常大,数据包之间的间隔最多可达30毫秒。此外,调度程序还会尝试通过在一毫秒内多次调用Runnable来弥补错过的周期(这对任何人都不意外)。

经过一些搜索,似乎有共识认为JVM不能保证任何形式的精确调度。然而,我决定自己做一些实验,得出了以下结论:

Runnable commTask = () -> {
    // volatile boolean controlled from the GUI
    while(deviceConnection) {
        // retrieve start time
        startTime = System.nanoTime();
        // time since commProtocol was last called
        timeDiff = startTime - previousTime;

        // if at least 5 milliseconds has passed
        if(timeDiff >= 5000000) {
            // handle communication
            commProtocol();
            // store the start time for comparison
            previousTime = startTime;
        }
    }
};

// commTask is started as follows
service = Executors.newSingleThreadScheduledExecutor();
service.schedule(commTask, 0, TimeUnit.MILLISECONDS);
这个结果非常出色。相邻的时间戳与预期的5毫秒间隔之间的差异从未超过0.1毫秒。尽管如此,这种技术似乎不太对劲,但我还没有想出其他有效的方法。我的问题基本上是,这种方法是否可行,如果不行,应该怎么做?
(我正在运行带有JDK 8_74的Windows 10)

1
你的解决方案可能会占用大量处理器资源;你可能想在while循环中添加一个微小的休眠以释放资源。 - FThompson
3
你可能会在这里得到一个很好的答案,但你也可以尝试 https://codereview.stackexchange.com/。 - DavidS
2
这是一篇你可能会喜欢的丰富文章,Ben:使用Java SE API开发实时软件(2014年的文章),其中引用了Java实时规范(JSR 1)和安全关键Java技术规范(JSR 302)。 - DavidS
2
你所拥有的东西看起来很像计算机游戏引擎的主循环。这意味着你的解决方案很好。添加Thread.sleep(0)以允许其他进程有机会休息,将执行器更改为Executors.newSingleThreadExecutor,然后你就拥有了一个典型的快速执行、受限制的、有产出的主循环。 - Steve McLeod
1
SteveMcLeod 把这个作为答案发出来,我会接受它的(最终我使用了 Thread.yield 替代)。 - Ben
显示剩余6条评论
1个回答

2

根据我在评论中收到的信息,我决定基本保持我的代码不变(除了我已经添加到while循环的Thread.yield())。我已经使用这个方法几个月了,对这种方法的性能非常满意。请参见下面的最终代码。

Runnable commTask = () -> {
    // volatile boolean controlled from the GUI
    while(deviceConnection) {
        // retrieve start time
        startTime = System.nanoTime();
        // time since commProtocol was last called
        timeDiff = startTime - previousTime;

        // if at least 5 milliseconds has passed
        if(timeDiff >= 5000000) {
            // handle communication
            commProtocol();
            // store the start time for comparison
            previousTime = startTime;
        }
        Thread.yield();
    }
};

// commTask is started as follows
service = Executors.newSingleThreadScheduledExecutor();
service.execute(commTask);

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