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

Tre*_*kaz 6 java time system-clock

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

然后它产生多个写入自己日志的工作进程。(我希望工人通过 master 登录,但由于某种原因,这个想法遭到了抵制。)

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

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

Bas*_*que 13

对 的调用System.currentTimeMillis及其现代替代品Instant.now都捕获了主机操作系统和底层计算机时钟硬件报告的当前时刻。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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我第一次运行该代码时,在输出的最后两行中发现了这样的跳转。第 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
Run Code Online (Sandbox Code Playgroud)

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

时钟重置

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

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

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

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

假时钟

在java.time 中,诸如Instant.now访问当前分配的Clock实现之类的调用。通过“当前分配”,我指的是在java.time 中,默认Clock对象可以被覆盖。通常这仅用于测试目的。各种Clock对象可以报告固定时刻、移动时刻,或者可以改变节奏报告。

所以请注意Clock,如果您的测试代码指定了一个替代Clock对象,则替代对象可能会故意告诉不同的时间。默认情况下,尽管您总是在方法调用执行时获得当前时刻。

结论

这里有一个主要含义:不能完全信任时间跟踪。当前时刻可能抓错了,抓拍的瞬间上报可能乱序。

因此,在调试或调查时,请始终将这个想法隐藏在您的脑海中:时间戳及其顺序可能不会告诉您全部真相。您最终无法 100% 确定地知道何时发生了什么。