Java中不要使用System.currentTimeMillis()调试测试延迟时间


System.currentTimeMillis()是所谓挂钟概念:当前时间与 UTC 时间 1970 年 1 月 1 日午夜之间的差值(以毫秒为单位)。它只是告诉您当前系统时间是多少。并且使用它会导致很多问题:
 
1. 粒度
System.currentTimeMillis()以毫秒为单位返回当前时间,但值的粒度取决于底层操作系统,并且可能大于基本单位。例如,许多操作系统以几十毫秒为单位测量时间。
假设您的操作系统以 100 毫秒为单位测量时间。在这种情况下,没有办法衡量需要比这更精确的任何东西。因此,如果某个操作需要 20 到 80 毫秒之间的某个时间,则您的测量结果为 0 或 100 毫秒(此精度可能会更差,例如:1 秒),更不用说测量亚毫秒级操作(微基准、纳米基准)。
 
2. 时间均匀性
您可能听说过地球自转存在不规则性(它以复杂的方式减慢和加速)。此外,地球自转会长期放缓。有一个标准,称为UT1(世界时的一个版本),它基于天文观测和地球自转。因此,UT1 并不总是均匀流动。因此,如果您的时间源基于此,则您有一个不均匀的时间源,它可以加速和减速,从而导致测量错误(请参阅:javadoc ofDate)。 System.currentTimeMillis()使用 UTC 而不是 UT1 所以它没有这个确切的问题。
 
3. 闰秒
UTC是通过精确的原子钟测量的,而UT1是基于天文观测的(参见上一节)。为了使两者保持一定程度的接近(<0.9 秒),UTC 偶尔会调整一秒。这种调整称为闰秒。由于气候和地质事件会影响地球的自转,UTC 闰秒是不规则且不可预测的。这些调整可能会导致测量错误,因为在测量过程中时钟可能会发生变化(请参阅:javadoc ofDate)。
 
4. 同步系统时钟
当时钟不以相同的速率运行时,可能会发生时钟漂移(它们不同步)。为了让它们彼此接近,时钟同步是必要的(参见:NTP)。由于您的系统时钟精度与原子钟的精度相差甚远,因此需要定期调整到UTC的几毫秒内。这可能意味着时间跳跃或使时钟走得更快或更慢,以便它会向 UTC 漂移。这些调整也会导致测量误差。

5. 夏令时
如果夏令时在此类测量中引起问题,则需要真正搞砸一些事情,但如果您需要处理时间,则这是一个非常常见的问题。我见过一次服务每年崩溃两次,你能猜到为什么吗?
  
解决方案:System.nanoTime()
解决方案非常简单:不要使用“挂钟”。在 Java 中:System.nanoTime()您可以访问专为测量经过时间而设计的高分辨率时间源(纳秒),它与系统时钟或任何“挂钟”无关,请参阅 的javadocSystem.nanoTime()。所以你可以这样做:

Response handleRequest(Request request) {
    long start = System.nanoTime();
    try {
        return doSomething(request);
    }
    finally {
        record(System.nanoTime() - start);
    }
}

如果在做其他性能测试,建议:

  • 如果你在做 (nano/micro/milli/macro) 基准测试:JMH
  • 如果您想为您的应用程序收集指标:Micrometer
  • 如果您想对您的 Web 服务进行性能测试:Gatling