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

时间:2011-05-06 09:43:28

标签: java sleep clock

让我烦恼的错误与this ticket相同。基本上,如果您将OS时钟更改为过去的日期,则更改时正在休眠的所有线程都不会被唤醒。

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

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

  • 的Thread.sleep(长)
  • Thread.sleep(long,int)
  • 的Object.wait(长)
  • Object.wait(long,int)
  • 的Thread.join(长)
  • Thread.join(long,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();

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

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

修改

Ingo建议不要触摸系统时钟。我同意通常不需要它。问题是我们无法控制客户对计算机的操作(我们计划销售数百份)。

更糟糕的是:我们的一台机器没有任何人工干预就会出现这个问题。我猜操作系统(Windows XP)定期将其时钟与RTC时钟同步,这使得OS时钟自然会及时回溯。

后记

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

  1. 仅在我的机器上(带有内核2.6.37且带有OpenJDK 64位1.6.0_22的archlinux),Thread.sleepObject.waitThread.joinLockSupport.parkNanos有同样的问题:只有当系统时钟到达唤醒的“目标”时间时,它们才会唤醒。但是,我的shell中的简单sleep不会出现问题。

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

  3. 所以,我所做的是通过更简单的实现替换了所有java的Timer。这解决了我的所有机器的问题(我只希望我的机器不仅仅是一个规则)。

5 个答案:

答案 0 :(得分:9)

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

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

编辑:

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

似乎还有很长的路可以解决这个问题,但这是我能想到的唯一可行的解​​决方法。此外,鉴于产生外部过程是一项相对昂贵的操作,如果您睡眠时间相对较长(如几百毫秒或更长时间),它可能效果最佳。对于较短的持续时间,它可能会继续颠覆CPU。

答案 1 :(得分:3)

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

(尽管如此,我同意,如果系统时钟发生变化,JVM应该检测到这一点并更新或唤醒休眠线程以解决问题。)

答案 2 :(得分:2)

请测试最新的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进行了测试。

答案 3 :(得分:0)

@Op。你已经实现了一些看起来像“忙碌的等待”的东西,并且总会占用大量的资源。

我同意其他人的观点,当你从夏天到冬天的时候,我不明白为什么你需要改变系统时钟。

答案 4 :(得分:0)

您不会为DST调整更改操作系统时间!它只不过是时区的变化。系统时钟应始终为GMT。您向用户显示的挂钟时间来自具有适当时区偏移的挂钟时间。