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

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

我正在开发的应用程序意味着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都有同样的问题(它们被阻塞直到达到“目标”时间)。

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

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

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

编辑:

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

似乎很难解决问题,但这是我能想到的唯一可行的解​​决方法。 此外,鉴于产生外部过程是一个相对昂贵的操作,如果你睡了相当长的时间(如几百毫秒或更长),它可能是最好的。 对于较短的持续时间,它可能只是继续颠簸CPU。

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

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

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

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

我用linux内核3.7.10测试过它。

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

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

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