`System.currentTimeMillis()` 在多个进程中是否正确?

Is `System.currentTimeMillis()` correct across multiple processes?

我们有一个主进程写入日志的情况。

然后它会生成多个写入自己日志的工作进程。 (我想让worker通过master登录,但是不知为何有人反对这个想法。)

我想知道的是,我可以相信最终出现在多个文件中的时间戳彼此一致吗?即,如果我将日志文件合并为一个按即时排序的文件,事件的顺序是否为真?跨所有可能的操作系统?

我问这个的原因是我有一个奇怪的情况,似乎在主进程报告工作进程有错误两秒后,工作进程记录了一个错误。就好像师父能够看到未来一样。 (我猜主人也是时间领主,但是呃...)

System.currentTimeMillis 的调用及其现代替代 Instant.now,都捕获主机 OS 和底层计算机时钟硬件报告的当前时刻。 Javadoc 和源代码承诺时钟“基于最佳可用系统时钟”。

所以,不,应该不跳入未来。每次调用这些方法中的任何一个时,您都会捕获当前时刻。

但是,您可能会看到跳入未来的错觉。发生这种情况的原因可能是:

  • 线程调度
  • 时钟重置
  • 假时钟

线程调度

这种错觉可能是由于捕获当前时刻之后发生的事情。捕获当前时刻后的一瞬间,该线程的执行可能会暂停。然后一些其他线程可能捕获稍后的时刻,继续报告那个时刻。最终,第一个线程恢复,并报告其较早捕获的时刻 — 但请注意该时刻的 报告 是如何发生的。

以这个示例代码为例。

package work.basil.example;

import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TellTime
{
    public static void main ( String[] args )
    {
        TellTime app = new TellTime();
        app.demo();
    }

    private void demo ( )
    {
        ExecutorService executorService = Executors.newCachedThreadPool();

        int countThreads = 15;
        List < Callable < Object > > tasks = new ArrayList <>( countThreads );
        for ( int i = 0 ; i < countThreads ; i++ )
        {
            Runnable tellTimeRunnable = ( ) -> System.out.println( Instant.now() );
            tasks.add( Executors.callable( tellTimeRunnable ) );
        }
        try
        {
            List < Future < Object > > list = executorService.invokeAll( tasks );
        }
        catch ( InterruptedException e )
        {
            e.printStackTrace();
        }
    }
}

我第一次 运行 该代码时,我在最后两行输出中发现了这样的跳转。第 4 行显示的时间比第 3 行早。第 5 行显示的时间更早。

2020-11-23T01:07:34.305318Z
2020-11-23T01:07:34.305569Z
2020-11-23T01:07:34.305770Z
2020-11-23T01:07:34.305746Z
2020-11-23T01:07:34.305434Z

在我这里的例子中,对 System.out.println 的调用在执行时被延迟了,所以一些较早的时刻被稍后报告了。同样,我怀疑在您的情况下,记录您捕获的瞬间的行为涉及各种延迟,因此一些较早的瞬间被稍后记录。

时钟重置

Stephen C points out 一样,计算机通常配置为根据来自时间服务器的信息自动调整硬件时钟。许多计算机中的硬件时钟没有您想象的那么准确。因此主机的时钟很可能会重置为更早或更晚的时间以纠正时间跟踪漂移。

请注意,当使用故障或耗尽 battery/capacitor 支持硬件时钟启动时,某些计算机会将其时钟重置回 epoch reference 点,例如 1970-01-01 00:00Z .在计算机有机会向时间服务器登记之前,该纪元参考时刻可能会被报告为当前时刻。

或者某些人可以手动调整计算机时钟的当前日期和时间。 :-(

您的代码可能会在此时钟调整的任一侧捕获当前时刻。现在,后来的事件似乎发生得更早。

假时钟

java.time中,Instant.now等调用访问当前分配的Clock implementation. By "currently assigned", I refer to the fact that in java.time, the default Clock object can be overridden. Usually that would be for testing purposes only. Various Clock object can report a fixed moment, a shifted moment, or may report with an altered cadence

因此请注意,如果您的测试代码指定了备用 Clock 对象,则备用 Clock 可能会故意显示不同的时间。默认情况下,您始终会在方法调用执行时获得当前时刻。

结论

这里有一个重要的含义:时间跟踪不能完全信任。当前时刻可能捕获不正确,捕获时刻的报告可能乱序。

因此,在调试或调查时,请始终将这个想法藏在脑海中:时间戳和它们的排序可能并没有告诉你全部真相。你最终无法 100% 确定什么时候发生了什么。