更改操作系统时间时 sleep() 中的 Java 错误:有解决方法吗?
令我烦恼的错误与这张票相同。基本上,如果您将操作系统时钟更改为过去的日期,则在更改时处于休眠状态的所有线程都不会唤醒。
我正在开发的应用程序旨在 24/24 运行,我们希望能够在不停止的情况下更改操作系统日期(例如,从夏令时切换到冬令时)。目前发生的情况是,当我们将日期更改为过去时,应用程序的某些部分就会冻结。我在多台计算机、Windows XP 和 Linux 2.6.37 以及最新的 JVM (1.6.0.22) 上观察到了这一点。
我尝试了许多Java睡眠原语,但它们都有相同的行为:
- Thread.sleep(long)
- Thread.sleep(long, int)
- Object.wait(long)
- Object.wait(long, int)
- Thread.join(long)
- 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();
问题是,这使得应用程序在空闲时的 CPU 使用率从 2% 增长到 15%。
您是否有解决原始问题的想法,或者您可以想出另一种方法来监视线程冻结的出现吗?
编辑
Ingo 建议不要碰系统时钟。我同意一般不需要。问题是我们无法控制客户如何使用他们的计算机(我们计划出售数百份)。
更糟糕的是:我们的一台机器在没有任何手动干预的情况下就出现了这个问题。我猜操作系统(Windows XP)定期将其时钟与RTC时钟同步,这使得操作系统时钟自然地回到过去。
结语
我发现我的问题中的一些陈述是错误的。我最初的问题实际上涉及两个不同的原因。现在,我可以肯定地说两件事:
仅在我的机器上(带有内核 2.6.37 的 archlinux 和 OpenJDK 64 位 1.6.0_22)、
Thread.sleep
、Object。 wait
、Thread.join
、LockSupport.parkNanos
也有同样的问题:它们仅在系统时钟达到唤醒的“目标”时间时才唤醒。但是,在我的 shell 中进行简单的sleep
并不会出现该问题。在我测试的所有机器上(包括我的机器),
java.util.Timer
和java.swing.Timer
都有同样的问题(它们被阻塞,直到“达到目标”时间)。
因此,我所做的就是用更简单的实现替换了所有 java 的 Timer。这解决了除我的机器之外的所有机器的问题(我只是希望我的机器是例外而不是规则)。
The bug that annoys me is the same than this ticket. Basically, if you change the OS clock to a date in the past, all the thread that were sleeping at the time of the change won't wake up.
The application I am developping is meant to be running 24/24, and we would like to be able to change the OS date without stopping it (for example, to switch from summer time to winter time). What happens for the moment is that when we change the date to the past, then some parts of the application just freeze. I observed that on multiple machine, on Windows XP and Linux 2.6.37, and with a recent JVM (1.6.0.22).
I tried many Java sleeping primitives, but they all have the same behavior :
- Thread.sleep(long)
- Thread.sleep(long, int)
- Object.wait(long)
- Object.wait(long, int)
- Thread.join(long)
- Thread.join(long, int)
- LockSupport.parkNanos(long)
- java.util.Timer
- javax.swing.Timer
Now, I am out of idea to work around this problem. I think there is nothing I can do to prevent the sleeping threads to freeze. But I would like, at least, to warn the user when a dangerous system clock change is detected.
I came up with a monitoring thread that detects such changes :
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();
The problem is that this makes the application grow from 2% CPU usage to 15% when idle.
Do you have an idea to work around the original problem, or can you think of another way to monitor the appearance of thread freeze ?
Edit
Ingo suggested not to touch the system clock. I agree that it's generally not needed. The problem is that we don't control what our clients do with their computers (we plan to sell hundred of copies).
Worse : one of our machine exhibits this problem without any manual intervention. I guess the OS (Windows XP) regularly synchronizes its clock to the RTC clock, and this makes the OS clock go back in time naturally.
Epilogue
I found out that some statements in my question were wrong. There are actually two separate causes involved in my initial problem. Now, I can say two things for sure :
On my machine only (archlinux with kernel 2.6.37 with an OpenJDK 64 bits 1.6.0_22),
Thread.sleep
,Object.wait
,Thread.join
,LockSupport.parkNanos
have the same problem : they wake up only when the system clock reaches the "target" time of awakening. However, a simplesleep
in my shell does not exhibit the problem.On all the machines I tested (included mine),
java.util.Timer
andjava.swing.Timer
have the same problem (they are blocked until the "target" time is reached).
So, what I've done is that I replaced all the java's Timer
by a simpler implementation. This solves the problem for all the machines but mine (I just hope my machine is an exception more than a rule).
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
根据 bug 票证,您的线程不会冻结,一旦时钟赶上修改前的位置,它们就会恢复(因此,如果他们将其向后移动一小时,您的线程将在 1 小时内恢复)。
当然,这仍然不是很有用。根本原因似乎是 Thread.sleep() 解析为一个系统调用,该调用将线程置于休眠状态,直到将来的某个特定时间戳,而不是指定的持续时间。要解决此问题,您需要实现自己的
Thread.sleep()
版本,该版本使用System.nanoTime()
而不是System.currentTimeMillis()< /code> 或任何其他与时间相关的 API。但是,如何在不使用内置 Thread.sleep() 的情况下做到这一点,我不能说。
编辑:
或者,如果您用另一种语言(例如 C 或您喜欢的其他语言)创建一些外部应用程序,该应用程序除了等待指定的持续时间然后退出之外什么也不做。然后,您可以生成此外部进程的一个新实例,然后对其调用 waitFor(),而不是在 Java 中调用 Thread.sleep()。这将使 Java 线程出于所有实际目的而“休眠”,只要您的外部应用程序能够休眠正确的持续时间,它就会在正确的时间恢复,而不会被冻结,也不会破坏 CPU。
解决这个问题似乎还有很长的路要走,但这是我能想到的唯一可行的解决方法。此外,考虑到生成外部进程是一项相对昂贵的操作,如果您睡眠的时间相对较长(例如几百毫秒或更长时间),它可能效果最好。对于较短的持续时间,它可能会继续破坏 CPU。
According to the bug ticket, your threads aren't frozen, they will resume once the clock catches up to where it was before it was modified (so if they moved it back an hour, your threads will resume in 1 hour).
Of course, that is still not very useful. The root cause seems to be that
Thread.sleep()
resolves to a system call that puts the thread to sleep until some specific timestamp in the future, rather than for a specified duration. To work around it you would need to implement your own version ofThread.sleep()
that usesSystem.nanoTime()
instead ofSystem.currentTimeMillis()
or any other time-dependent API. How to do that without using the built-inThread.sleep()
I can't say, however.Edit:
Or, what if you create some external app in another language (like C or whatever else you prefer) that does nothing but wait for a specified duration and then exit. Then instead of calling Thread.sleep() in Java, you can spawn a new instance of this external process, and then call waitFor() on it. This will "sleep" the Java thread for all practical purposes, and so long as your external app is able to sleep for the correct duration, it will resume at the correct time without getting frozen and without thrashing the CPU.
Seems like a long way to go to fix the issue, but it's the only feasible workaround that I can think of. Also, given that spawning an external process is a relatively expensive operation, it probably works best if you are sleeping for a relatively long time (like several hundred ms or more). For shorter durations it might just continue thrashing the CPU.
正如其他人所说,您绝对不必更改系统时钟。时间戳(自纪元以来的毫秒数)在世界各地的所有计算机上都是一致的,但本地时间取决于您的位置、对夏令时的观察等等。因此,问题出在操作系统区域设置和时间/日期设置上。
(不过,我同意如果系统时钟确实发生变化,JVM 应该检测到这一点并更新或唤醒休眠线程来解决该问题。)
As others have said, you definitely shouldn't have to change the system clock. The timestamp (milliseconds since the epoch) is consistent across all computers across the world, but the local time depends on your location, observation on Daylight Savings Time and so on. Therefore, the problem is with the OS locale and time/date settings.
(Still, I agree that if the system clock does change, the JVM should detect this and update or awaken sleeping threads to combat the problem.)
请测试最新的jre 1.7.0_60。它至少解决了 2009 年以来发布的 glibc 版本的系统因系统时间移至过去而引起的问题。
相关 bug http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6900441 已修复,因此您提到的所有函数 (
Thread.sleep, Object.wait、Thread.join、LockSupport.parkNanos、java.util.Timer 和 java.swing.Timer
)应该按预期工作。我已经用 Linux 内核 3.7.10 对其进行了测试。
Please test the latest jre 1.7.0_60. It resolves the described problem caused by a system time shift to the past at least for systems with a glibc version released since 2009.
Related bug http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6900441 has been fixed and therefore all functions mentioned by you (
Thread.sleep, Object.wait, Thread.join, LockSupport.parkNanos, java.util.Timer and java.swing.Timer
) should work as expected.I have tested it with a linux kernel 3.7.10.
@Op。您已经实现了一些看起来像“忙等待”的东西,并且总是会消耗大量资源。
我同意其他人的观点,我不明白为什么当你从夏季时间进入冬季时间时需要更改系统时钟。
@Op. You have implemented something that looks like "busy waiting", and that will always consume lots of resources.
I agree with the others, I don't see why you need to change the system clock when you go from summer to winter time.
您无需更改操作系统时间来进行 DST 调整!无非是时区的改变。系统时钟应始终为 GMT。您向用户显示的挂钟时间源自具有正确时区偏移的挂钟时间。
You don't change OS time for DST adjustments! It's nothing more than a Time Zone change. System clock should always be GMT. And the wall clock time that you display to the user is derived from that with the proper time zone offset.