Windows 10中的Java Thread.sleep()在S3睡眠状态下停止。

13

有一个桌面应用程序,使用Thread.sleep()实现长时间(几分钟或几小时)的延迟。自Windows XP到(至少)Windows 7,这个应用程序一直运行良好。该应用程序计算需要多久才能做某事,然后执行Thread.sleep(msToWait)。即使系统在等待期间进入S3睡眠状态,这也可以正常工作。

然而,在Windows 10上,如果机器处于S3状态,则Thread.sleep()后的代码不会“准时”执行。似乎机器从“msToWait”开始执行代码,加上机器处于S3中的时间(现在不确定,但很可能是这样)。

早期版本的Windows没有表现出这种行为;Thread.sleep()后的代码始终等待正确的时间,无论睡眠状态如何。

测试使用的是当前的JVM 1.7。

这是Windows 10的bug吗?这是JVM的bug吗?有没有解决办法?

额外的数据:

开发了一个测试程序和过程。该过程是运行程序,使机器睡眠约一分钟,然后唤醒机器并等待程序完成。

如果在报告为8的Windows 10上运行具有JVM版本:25.40-b25的程序,则会失败:

C:\Users\Tester\Downloads>SleepTester.exe
Wed Apr 01 10:47:35 PDT 2015 Using default number of minutes: 5
Wed Apr 01 10:47:35 PDT 2015 You can use "SleepTester -minutes 10" to have it sleep for 10 minutes, for example.
Wed Apr 01 10:47:35 PDT 2015 JVM Version: 25.40-b25 Windows Version: Windows 8
Wed Apr 01 10:47:35 PDT 2015 The program will now wait for 5 minutes.  Expect wrap-up at Wed Apr 01 10:52:35 PDT 2015
Wed Apr 01 10:53:38 PDT 2015 The system has come through the Thread.sleep(300000).
Wed Apr 01 10:53:38 PDT 2015 This should be a low number: 63589
Wed Apr 01 10:53:38 PDT 2015 This appears to be operating incorrectly...the expected sleep time has NOT been achieved.
Wed Apr 01 10:53:38 PDT 2015 Program is ending.

如果在Windows 7上运行该进程,则不会失败。

Wed Apr 01 17:12:18 EDT 2015 Java Runtime Version: 1.8.0_31-b13 JVM Version: 25.31-b07 Windows Version: Windows 7
Wed Apr 01 17:12:18 EDT 2015 The program will now wait for 6 minutes.  Expect wrap-up at Wed Apr 01 17:18:18 EDT 2015
Wed Apr 01 17:18:18 EDT 2015 The system has come through the Thread.sleep(360000). 
Wed Apr 01 17:18:18 EDT 2015 This should be a low number: 0
Wed Apr 01 17:18:18 EDT 2015 Program is ending.

这是测试程序:

import java.util.Date;

public class SleepTester {

private static int mMinutes;
private static int mDefault = 5;

public static void main(String[] args) throws Exception {
    for (int iArg = 0; iArg < args.length; ++iArg) {
        if (args[iArg].equals("-minutes") && (iArg + 1) < args.length) {
            mMinutes = Integer.parseInt(args[++iArg]);
        }
    }

    if (mMinutes == 0) {
        mMinutes = mDefault;
        System.out.println(new Date() + " Using default number of minutes: " + mDefault);
        System.out.println(new Date() + " You can use \"SleepTester -minutes 10\" to have it sleep for 10 minutes, for example.");
    }
    
    System.out.println(new Date() + " Java Runtime Version: " + System.getProperty("java.runtime.version") + " JVM Version: " + System.getProperty("java.vm.version") + " Windows Version: " + System.getProperty("os.name"));
    long msDelay = mMinutes * 60 * 1000;
    long wakePoint = new Date().getTime() + msDelay;
    System.out.println(new Date() + " The program will now wait for " + mMinutes + " minutes.  Expect wrap-up at " + new Date(wakePoint));
    Thread.sleep(msDelay); // If the machine goes into S3 during this interval, it should not matter, as long as it's awake when it fires.
    System.out.println(new Date() + " The system has come through the Thread.sleep(" + msDelay + "). ");
    long msAccuracy = Math.abs(new Date().getTime() - wakePoint);
    System.out.println(new Date() + " This should be a low number: " + msAccuracy);
    if (msAccuracy > 1000) System.out.println(new Date() + " This appears to be operating incorrectly...the expected sleep time has NOT been achieved.");
    System.out.println(new Date() + " Program is ending.");
}
}

我意识到我可以尝试其他睡眠方法,但我认为既然我已经记录下来并发表在这里,在尝试其他方法之前,我会把它发布在这里。

其他信息:此错误似乎也出现在Windows 8中(但不是7或更早版本)。

附加4/4/2019

该问题可在bugs.java.com的以下网址[JDK-8221971]上看到。

有一些早期的错误链接到该错误。来自链接JDK-8146730错误的评论:

17-04-2017 对这个问题有什么最新进展吗?

04-04-2019 已经被延迟。这是一个低优先级、复杂的问题,没有人积极处理它。

附加2/17/2021

这可能是由于Windows操作系统对超时响应方式的改变所致。即使直接使用Windows API,我也不确定如何实现使旧的和新的Windows操作系统表现相同的目标。

Windows XP、Windows Server 2003、Windows Vista、Windows 7、Windows Server 2008和Windows Server 2008 R2:dwMilliseconds值包括在低功耗状态下花费的时间。例如,计时器在计算机处于睡眠状态时仍在倒计时。

Windows 8、Windows Server 2012、Windows 8.1、Windows Server 2012 R2、Windows 10和Windows Server 2016:dwMilliseconds值不包括在低功耗状态下花费的时间。例如,计时器在计算机处于睡眠状态时不再倒计时。

https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobjectex


不确定错误在哪里,但你能否尝试使用TimeUnit.XXX.sleep(someQuantity)或者Thread.nanoSleep()(前者只是后者的包装器),看看是否有所改善?另外,使用的JVM是什么版本? - fge
我建议尝试使用不同的JVM版本(也许会有所不同,如果您使用的JVM版本比Windows 10旧或新)。然后,有一种非常可靠的方法来确定是否是JVM错误:去向Oracle写一个缺陷并观察发生了什么;-) - GhostCat
1
如果通过Executors.newScheduledThreadPool(1)创建一个ScheduledExecutorService,并使用schedule提交一个任务,是否也存在相同的问题值得一看。 - Powerlord
1
@Dale,测试一下Java 8u40可能是个好主意。 - Powerlord
1
我不知道为什么以前它能工作,但我认为它的行为是可以预料的。无论如何,在Windows上这不是一个好的方法。你应该使用可等待定时器,使用绝对时间(相对时间将完全像Sleep一样)。它们自Windows XP以来就存在:https://learn.microsoft.com/en-us/windows/desktop/sync/waitable-timer-objects(如果机器配置了,它们甚至可以唤醒机器,如果它进入睡眠状态并超时)。我可以提供一个C++示例,但我不知道如何从Java中使用它们 :-) - Simon Mourier
显示剩余5条评论
2个回答

6
这是预期的、有效的行为。文档非常明确地指出:
这些休眠时间不能保证精确,因为它们受底层操作系统提供的设施的限制。
并且:
无论如何,您都不能假定调用sleep会精确地挂起线程指定的时间段。

不考虑旧版Windows在S3期间没有“停止时间”的情况,这是意料之外的。我并不要求精确度,这正是这两个片段所解决的问题。我只是想知道为什么新版本的Windows行为明显不同。 - Dale
所引用的文档没有提到底层操作系统的挂起状态,而这恰恰是本问题的核心。 - Dale
2
规范说明精度不保证,因此您可能不应期望精度。一个底层操作系统恰好提供了精度并不意味着Java有义务在未来永远保持那种精度。Java从未承诺过精度。 - nobody
我们在精度问题上存在分歧。显然,如果睡眠时间不准确超过_小时_(或更多),您认为这是一个精度问题。而我则不这样认为。 - Dale
@walen 我认为这是一个编程问题,因为在Win7和Win10中行为不同的程序的用户只会看到程序表现出错误的行为,而不知道为什么。程序员可以看到在这两个操作系统之间执行不同的代码行。 - Dale
显示剩余2条评论

3

这可能是Windows 10的一个错误。即使像开始菜单这样的操作系统重要功能在Metro中完成,有时候也会因为受到防火墙或禁用等问题而无法及时从挂起状态中唤醒。建议使用ProcessHacker或一些Sysinternals工具检查线程/进程状态并尝试找到解决方法。然后再检查Windows事件日志等。

或者像将睡眠替换为在命令行中执行的信号量和休眠那样做一些傻事。

文档明确表示它应该能够及时唤醒,但您不应将其作为某些数据线路上的输出计时器、播放音乐等的依赖项,但花费100倍的时间是有所不同的。


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