更改操作系统时间时 sleep() 中的 Java 错误:任何解决方法?

2022-09-03 07:17:18

惹恼我的错误与此证相同。基本上,如果将操作系统时钟更改为过去的日期,则在更改时处于睡眠状态的所有线程都不会唤醒。

我正在开发的应用程序旨在全天候运行,我们希望能够在不停止的情况下更改操作系统日期(例如,从夏时切换到冬令时)。目前发生的事情是,当我们将日期更改为过去时,应用程序的某些部分就会冻结。我在多台机器上,Windows XP和Linux 2.6.37以及最近的JVM(1.6.0.22)上观察到了这一点。

我尝试了许多Java睡眠原语,但它们都有相同的行为:

  • 线程.睡眠(长)
  • Thread.sleep(long, int)
  • Object.wait(long)
  • Object.wait(long, int)
  • 线程.连接(长)
  • Thread.join(long, int)
  • LockSupport.parkNanos(long)
  • java.util.Timer
  • javax.swing.Timer

现在,我的想法是想解决这个问题。我认为我无能为力,以防止沉睡的线程冻结。但是,我想至少在检测到危险的系统时钟变化时警告用户。

我想出了一个监控线程来检测这些变化:

    Thread t = new Thread(new Runnable() {
        @Override
        public void run() {
            long ms1 = System.currentTimeMillis();
            long ms2;
            while(true) {
                ms2 = ms1;
                ms1 = System.currentTimeMillis();
                if (ms1 < ms2) {
                    warnUserOfPotentialFreeze();
                }
                Thread.yield();
            }                    
        }
    });
    t.setName("clock monitor");
    t.setPriority(Thread.MIN_PRIORITY);
    t.setDaemon(true);
    t.start();

问题是,这会使应用程序在空闲时从 2% 的 CPU 使用率增长到 15%。

您是否有解决原始问题的想法,或者您能想到另一种方法来监控线程冻结的外观吗?

编辑

Ingo建议不要触摸系统时钟。我同意它通常不需要。问题在于,我们无法控制客户如何处理他们的计算机(我们计划出售数百份副本)。

更糟糕的是 :我们的一台机器在没有任何手动干预的情况下表现出此问题。我猜操作系统(Windows XP)会定期将其时钟与RTC时钟同步,这使得操作系统时钟自然地回到过去。

结语

我发现我的问题中的一些陈述是错误的。实际上,我最初的问题涉及两个不同的原因。现在,我可以肯定地说两件事:

  1. 仅在我的机器上(内核为2.6.37的archlinux,OpenJDK 64位为1.6.0_22),,,,具有相同的问题:只有当系统时钟达到唤醒的“目标”时间时,它们才会唤醒。但是,我的shell中的一个简单的问题并没有表现出问题。Thread.sleepObject.waitThread.joinLockSupport.parkNanossleep

  2. 在我测试的所有机器上(包括我的机器),并且有同样的问题(它们被阻止,直到达到“目标”时间)。java.util.Timerjava.swing.Timer

所以,我所做的是,我用一个更简单的实现取代了所有的java。这解决了除我的机器以外的所有机器的问题(我只是希望我的机器是一个例外而不是一个规则)。Timer


答案 1

根据错误票证,你的线程不会被冻结,一旦时钟赶上修改之前的位置,它们就会恢复(所以如果他们把它移回一个小时,你的线程将在1小时内恢复)。

当然,这仍然不是很有用。根本原因似乎是解析为系统调用,该调用将线程置于休眠状态,直到将来的某个特定时间戳,而不是在指定的持续时间内。要解决此问题,您需要实现自己的版本,而不是或任何其他依赖于时间的API。但是,如何在不使用内置功能的情况下做到这一点,我不能说。Thread.sleep()Thread.sleep()System.nanoTime()System.currentTimeMillis()Thread.sleep()

编辑:

或者,如果你使用另一种语言(如 C 或你喜欢的任何其他语言)创建一些外部应用,这些应用除了等待指定的持续时间然后退出之外什么都不做,该怎么办?然后,您可以生成此外部进程的新实例,然后在其上调用waitFor(),而不是在Java中调用Thread.sleep()。这将出于所有实际目的“休眠”Java线程,并且只要您的外部应用程序能够在正确的持续时间内休眠,它就会在正确的时间恢复而不会被冻结,也不会破坏CPU。

解决问题似乎还有很长的路要走,但这是我能想到的唯一可行的解决方法。此外,鉴于生成外部进程是一个相对昂贵的操作,如果您处于相对较长的睡眠时间(例如几百毫秒或更长时间),它可能效果最好。在较短的持续时间内,它可能只是继续撞击CPU。


答案 2

正如其他人所说,您绝对不应该更改系统时钟。时间戳(自纪元以来的毫秒)在全世界所有计算机上都是一致的,但本地时间取决于您的位置、对夏令时的观察结果等。因此,问题出在操作系统区域设置和时间/日期设置上。

(不过,我同意如果系统时钟确实发生了变化,JVM应该检测到这一点,并更新或唤醒休眠线程来解决这个问题。