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



惹恼我的bug和这张票是一样的。基本上,如果您将操作系统时钟更改为过去的日期,那么在更改时处于睡眠状态的所有线程都不会唤醒。

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

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

  • thread . sleep(长)
  • 线程。睡眠(长,int)
  • Object.wait(长)
  • 对象。等待(长,int)
  • Thread.join(长)
  • 线程。加入(长,int)
  • LockSupport.parkNanos(长)
  • 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();

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

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

编辑

Ingo建议不要触碰系统时钟。我同意这通常是不必要的。问题是我们不能控制我们的客户用他们的电脑做什么(我们计划卖几百台)。

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

后记

我发现我的问题中的一些陈述是错误的。我最初的问题实际上有两个不同的原因。现在,我可以肯定地说两件事:
  1. 仅在我的机器上(archlinux内核2.6.37与OpenJDK 64位1.6.0_22),Thread.sleep, Object.wait, Thread.join, LockSupport.parkNanos有同样的问题:它们仅在系统时钟达到唤醒的"目标"时间时唤醒。然而,在我的shell中,一个简单的sleep没有出现这个问题。

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

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

根据bug ticket,您的线程不会冻结,一旦时钟赶上它被修改之前的位置,它们将恢复(因此,如果它们将时钟移回一小时,您的线程将在1小时内恢复)。

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

编辑:

或者,如果你用另一种语言(比如C或其他你喜欢的语言)创建一些外部应用程序,它什么都不做,只是等待指定的持续时间,然后退出。然后,不用在Java中调用Thread.sleep(),而是可以生成这个外部进程的一个新实例,然后在它上面调用waitFor()。这将"休眠"Java线程为所有实际目的,只要你的外部应用程序能够睡眠正确的持续时间,它将恢复在正确的时间没有冻结,没有抖动CPU。

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

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

(尽管如此,我同意如果系统时钟确实改变了,JVM应该检测到这一点,并更新或唤醒正在睡觉的线程来解决这个问题)

请测试最新的jre 1.7.0_60。至少对于2009年以后发布的glibc版本的系统来说,它解决了上述由系统时移到过去所引起的问题。

相关的错误http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6900441已被修复,因此您提到的所有功能(Thread.sleep, Object.wait, Thread.join, LockSupport.parkNanos, java.util.Timer and java.swing.Timer)应按预期工作。

我已经在linux内核3.7.10上测试了它。

@Op。你已经实现了一些看起来像"忙碌等待"的东西,并且总是会消耗大量资源。

我同意其他人的看法,我不明白为什么当你从夏令时间转到冬令时间时,你需要改变系统时钟。

您不更改操作系统时间为夏时制调整!只不过是时区变了。系统时钟应该始终为GMT。而你向用户显示的挂钟时间则是从带有适当时区偏移量的挂钟时间派生出来的。

相关内容

最新更新