首頁 > Java > 為什麼在大時間視窗上計算的經過時間在 System.currentTimeMillis 與 System.nanoTime 之間有高達 100+ 毫秒的差異

為什麼在大時間視窗上計算的經過時間在 System.currentTimeMillis 與 System.nanoTime 之間有高達 100+ 毫秒的差異

王林
發布: 2024-02-09 10:24:08
轉載
931 人瀏覽過

php小編蘋果為大家解答為什麼在大時間視窗上計算的經過時間在 System.currentTimeMillis 與 System.nanoTime 之間會有高達 100 毫秒的差異。這個問題涉及到電腦系統的底層機制和作業系統的調度方式。在計算經過時間時,System.currentTimeMillis 使用的是作業系統提供的牆鐘時間,而System.nanoTime 使用的是CPU時鐘週期。由於作業系統的調度機制和硬體時鐘的非同步性,這兩種計時方法會導致時間差異。具體的原因和解決方案將在接下來的文章中詳細介紹。

問題內容

我正在嘗試創建一個可在同一jvm 中進行微秒進動排序的ksuid,因為currenttimemillis 僅給出毫秒精度,考慮將currenttimemillis() 和nanotime()一起使用,收集第一個瞬間和第二個瞬間開始的值計算自第一個時間瞬間以來經過的微秒數的nanotime 差異。使用從 nanotime 計算出的經過時間並將其添加到初始即時毫秒中,以便我們可以在 jvm 啟動後的任何即時時間(均在同一 jvm 內)以微秒精度計算 epoch。

//靜態收集初始nanotime和currentmillis 最終長 inittimenanos = system.nanotime();最終長 inittimemillis = system.currenttimemillis();

//從這裡開始,當需要微/奈秒精度的當前時間時,從nanotime計算經過的時間並將經過的毫秒添加到initialmillis,剩下的經過時間給出微秒精度的時間

final long elapsednanotime = system.nanotime() - inittimenanos;最終雙精度 elapsedmillisfromnanotime = elapsednanos / (1000000.0);

#//自jvm啟動以來單調遞增的時間戳毫秒(epoch,我們可能不稱之為epoch) final 長計算當前時間millis = inittimemillis elapsedmillisfromnanotime; final long nanosprecision = elapsednanos % 1000000; //來自nanotime的附加時間精度

考慮使用這些值來產生單調遞增的ksuid,它可以近似表示當前時間,但保證根據同一jvm 內的創建時間進行排序,因為currenttimemillis 不提供單調遞增的時間戳保證,考慮使用這種方法生成大約接近真實時間戳的單調遞增時間戳(可能有幾毫秒的差異,除非對紀元時間進行閏秒調整)。使用 epoch 和 nanotime 計算出的經過時間預計有幾毫秒的差異,但實際差異非常頻繁地變化,當我在測試以下運行 48 小時時,觀察到這兩者之間的差異高達 150 毫秒。大多數情況下,使用 nanotime 計算的經過時間高於使用 currenttimemillis 計算的經過時間,且觀察到的時間範圍為 -2 毫秒到 150 毫秒。

final long elapsedmillis = system.currenttimemillis() - inittimemillis; //來自system.currenttimemillis()的經過時間毫秒 最終雙方差millis = elapsedmillisfromnanotime - elapsedmillis; //經過時間方差

我是否遺漏了任何有關 jvm 時間保證的內容,計算單調遞增近似時間戳的方法是否錯誤? (這不會用作系統中的真實紀元,僅用於生成 uuid,uuid 也代表同一 jvm 內的近似時間戳即時)。

//測試類別

package org.example;

import java.util.concurrent.TimeUnit;

public class Main {
    public static void main(String[] args) throws Exception {
        final long initTimeNanos = System.nanoTime();
        final long initTimeMillis = System.currentTimeMillis();
        System.out.println("Nanos: " + initTimeNanos);
        System.out.println("Millis: " + initTimeMillis);

        while (true) {
            final long currentNanos = System.nanoTime();
            final long elapsedNanos = currentNanos - initTimeNanos;
            final double elapsedMillisFromNanos = elapsedNanos / 1000000.0;
            final long elapsedMillis = System.currentTimeMillis() - initTimeMillis;
            final double varianceMillis = elapsedMillisFromNanos - elapsedMillis;
            if (Math.abs(varianceMillis) > 1) {
                System.out.printf("\nVariance Observed: %.6f\n", varianceMillis);
                System.out.printf("Elapsed Time: %.6fms (from System.nanoTime)\n", elapsedMillisFromNanos);
                System.out.printf("Elapsed Time: %dms (from System.currentTimeMillis)\n", elapsedMillis);
            }
            if (elapsedMillis > TimeUnit.HOURS.toMillis(48)) {
                break;
            }
            Thread.sleep(5000);
            System.out.print(".");
        }
    }
}
登入後複製

為什麼經過的時間變異會不斷改變? 如果jvm 連續運行一年,我們可以預期的最大方差是多少(任何jvm 對此上限或下限的保證,使用mac 和windows 進行測試,mac 給出了方差的緩慢增長,windows 的速度要快得多) ?

我預期經過的時間變化小於 10 毫秒,變化變化頻率較低。 但實際觀察是變異數不斷變化,上下波動,48小時內觀察到最多150毫秒

一種解釋是基於 ntp 的時間塗抹。更一般地說,奈米時間和 ctm 測量完全不同的東西,你不能將它們混合。

nanotime 有一個任意的 0 點(獲得 0 的 nanotime 沒有特殊意義),因此,除了將它返回的內容與不同 nanotime 調用的結果進行比較之外,根本沒有必要調用它。 nanotime 追蹤經過的時間,就是這樣。

system.ctm 取得系統時鐘。如果您使用posix date 命令或在系統設定中編輯系統的時間設置,則不會影響nanotime,但會變更system.ctm 傳回的內容。 ctm 通常也比 nanotime 慢得多。 ctm 還有一個明確定義的 0 - 表示 utc 1970 年 1 月 1 日午夜。

問題:「我希望當前時間符合奈秒精度的系統時鐘」在 jvm 上不可能

时间涂抹是指某些网络时间守护程序注意到您的系统时钟略有偏差,并且不只是将系统时钟编辑到正确的时间,而是引入了涂抹:假设您比实时时间“提前”了 400 毫秒。 ntp 可以将您的时钟向后设置 400 毫秒,但许多日志记录系统假设 system.ctm 不会向后移动(这是一个不正确但广泛应用的假设)。

这可以通过让时间放慢一段时间来“修复”:每经过 100 毫秒,ntp 守护进程就会将时钟“保持”在同一毫秒上一毫秒。每 100 毫秒就赶上 1 毫秒,因此在 400,000 毫秒(仅 400 秒)内,时钟恢复与网络同步,并且日志记录根本不受影响。

但是,这显然会完全破坏 system.ctm 和 nanotime 之间的任何关系!

大多数 ntp 都是这样涂抹的(它们也会向前涂抹 - 如果您的系统时钟落后,它不仅仅会向前跳跃:这会使日志撒谎(使其看起来就像两个事件之间存在一些间隙)比实际大得多),因此每 100 毫秒,ntp 就会使时钟跳过一毫秒,类似这样的事情,以赶上。

...但我需要单调递增的时间戳!

那么 nanotime 就无关紧要了。不要使用它。

拥有一些提供 id 的集中“商店”。一种实现:

class TimeStore {
  long lastVal = 0L;

  public synchronized long get() {
    long mark = System.currentTimeMillis() << 4;
    return lastVal = Math.max(mark, lastVal + 1);
  }
}
登入後複製

这将返回当前时间,左移 4 位,并将填充此移位“释放”的 16 个值,以便能够在同一时间生成单调递增值,最多 16 次;同一毫秒内的任何进一步请求都会潜入下一毫秒。

尽管如此,这可能并不比nanotime慢。

rzwitserloot的回答是正确的。我将向您提供我对各种问题的看法。

currenttimemillisnanotime 无关

system.currenttimemillissystem.nanotime 彼此无关。

  • currenttimemillis 从主机的硬件时钟单元检索当前日期和时间,由主机操作系统管理。
  • nanotime 来自主机 cpu 保存的计数。

currenttimemillis

所以首先要了解人类的年月日和时分秒的概念。传统计算机中使用的时钟硬件的分辨率有限,有些是毫秒,有些是微秒,但没有纳秒。

由于各种原因,本次通话报告的日期和时间可能会有所不同。其中一个原因是,在电池电量耗尽的情况下启动的计算机会将其时钟重置为默认时刻,直到通过调用时间服务器进行纠正。另一个原因是系统管理员或用户可能会更改日期时间。还有一个原因是硬件时钟可能无法很好地保持时间,并且会通过调用时间服务器在更新之间运行得快或慢。

nanotime

nanotime 调用以纳秒计数形式告知经过的时间。但这个流逝的时间与日历和墙上的时钟无关。此调用仅返回经过的纳秒的单调计数。

这个计数非常准确,因为它来自计算机的“跳动的心脏”。返回的数量始终在增加,直到达到 long.max_value,然后环绕到 long.min_value。这给出了大约 292 年的范围,但这并不意味着从现在起 292 年。计数的起点未指定。在某些 java 实现中,您可能会看到计数在计算机启动时开始。但并不能保证这一点。

问题

您可能会考虑寻找 新 6、7、8 版本 的实现a href="https://en.wikipedia.org/wiki/universally_unique_identifier" rel="nofollow noreferrer">uuid 被提议给 ietf 进行标准化。

currenttimemillis 在现代 java 中已被 java.time.instant 类取代。调用 instant.now 捕获 utc 中的当前时刻。

java 9+ 中的常见实现以微秒为单位报告,但在 java 8 中以毫秒为单位。 instant 类能够以纳秒为单位。

聰明的主意。但不現實。如上所述,currenttimemillisnanotime 不相關。最重要的是,currenttimemillis 的結果可能會因多種原因而有所不同。同時,nanotime 計數可能會隨著主機的每次啟動而變化,並且肯定不會對應於其他電腦上進行的任何 nanotime 呼叫。

是的,您忽略了這樣一個事實:java 規範沒有做出您似乎假設的任何保證。

javadoc 中唯一的保證是 nanotime 是“高分辨率”,至少與 currenttimemillis 一樣好。並且無法保證您預計何時能迎來 292 年的終點。

同時,currenttimemillis是一個移動目標,可以隨時改變,向前或向後移動。

這是不可預測的。

java 規格並沒有做出這樣的保證。

以上是為什麼在大時間視窗上計算的經過時間在 System.currentTimeMillis 與 System.nanoTime 之間有高達 100+ 毫秒的差異的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
來源:stackoverflow.com
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
最新問題
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板