ホームページ  >  記事  >  大きな時間枠で計算された経過時間が、System.currentTimeMillis と System.nanoTime で最大 100 ミリ秒以上異なるのはなぜですか

大きな時間枠で計算された経過時間が、System.currentTimeMillis と System.nanoTime で最大 100 ミリ秒以上異なるのはなぜですか

王林
王林転載
2024-02-09 10:24:08815ブラウズ

php エディター Apple は、大きな時間枠で計算された経過時間が System.currentTimeMillis と System.nanoTime で最大 100 ミリ秒異なる理由を説明します。この問題には、コンピュータ システムの基礎となるメカニズムとオペレーティング システムのスケジューリング方法が関係します。経過時間を計算するとき、System.currentTimeMillis はオペレーティング システムによって提供される実時間を使用しますが、System.nanoTime は CPU クロック サイクルを使用します。オペレーティング システムのスケジューリング メカニズムとハードウェア クロックの非同期性により、これら 2 つのタイミング方法では時間差が生じます。具体的な原因と解決策は以下の記事で詳しく紹介しています。

質問内容

同じjvmでマイクロ秒の歳差ソートができるksuidを作成しようとしています。currenttimemillisはミリ秒の精度しか与えないため、currenttimemillis()とnanotime()を一緒に使用することを検討してください。 、最初の瞬間から経過したマイクロ秒数のナノタイム差は、最初の瞬間と 2 番目の瞬間から始まる値を収集することによって計算されます。ナノタイムから計算された経過時間を使用し、それを最初の瞬間のミリ秒に追加します。これにより、jvm の開始後の任意の瞬間に (すべて同じ jvm 内で) エポックをマイクロ秒の精度で計算できます。

//初期ナノタイムと現在のミリスの静的コレクション 最終の長い inittimenanos = system.nanotime(); 最終の長い inittimemillis = system.currenttimemillis();

//今後、マイクロ/ナノ秒精度の現在時刻が必要な場合は、ナノタイムから経過時間を計算し、経過ミリ秒を初期ミリ秒に加算し、残りの経過時間でマイクロ秒精度の時刻を求めます

final long elapsednanotime = system.nanotime() - inittimenanos;final double elapsedmillisfromnanotime = elapsednanos / (1000000.0);

//jvm が開始されてから単調に増加するタイムスタンプ ミリ秒 (エポック、エポックとは呼ばないかもしれません) 最終の長い計算現在時間ミリス = inittimemillis elapsedmillisfromnanotime; 最終的な長いナノ精度 = elapsednanos % 1000000; //ナノタイムからの追加の時間精度

これらの値を使用して単調増加する ksuid を生成することを検討してください。これは現在時刻を近似できますが、同じ jvm 内の作成時刻に従ってソートされることが保証されています。currenttimemillis は単調増加するタイムスタンプ保証を提供していないため、考慮してください。このメソッドを使用して、実際のタイムスタンプにほぼ近い単調増加タイムスタンプ (エポック タイムにうるう秒の調整が行われない限り、数ミリ秒異なる可能性があります) を生成します。エポックとナノタイムを使用して計算された経過時間は数ミリ秒異なることが予想されていましたが、実際の差は非常に頻繁に変化し、以下のテストを 48 時間実行したところ、両者の間に最大 150 ミリ秒の差が観察されました。ほとんどの場合、nanotime を使用して計算された経過時間は、currenttimemillis を使用して計算された経過時間よりも長く、観測される時間の範囲は -2 ミリ秒から 150 ミリ秒の範囲です。

Final long elapsedmillis = system.currenttimemillis() - inittimemillis; //system.currenttimemillis() からの経過時間ミリ秒 最後の二重差 millis = elapsedmillisfromnanotime - elapsedmillis; //経過時間分散

JVM のタイミング保証について何か見逃していますか? 単調増加する近似タイムスタンプを計算する方法が間違っていますか? (これはシステム内の実際のエポックとしては使用されず、同じ jvm 内のおおよそのタイムスタンプの瞬間も表す uuid を生成するためだけに使用されます)。

//テストクラス

リーリー

経過時間の差異が変化し続けるのはなぜですか? jvm を 1 年間継続的に実行した場合に予想できる最大分散はどれくらいですか (どの jvm もこの上限または下限を保証しており、Mac と Windows でテストしましたが、Mac では分散の増加が遅く、Windows ははるかに高速でした)。

経過時間の変動は 10 ミリ秒未満であり、変更の発生頻度は低いと予想します。 しかし、実際の観察では、分散は常に変化しており、上下に変動しており、48 時間以内に最大 150 ミリ秒が観察されています。

解決策

説明の 1 つは、ntp に基づく時間スミアリングです。より一般的には、nanotime と ctm はまったく異なるものを測定するため、それらを混合することはできません。

nanotime には任意の 0 点があります (nanotime が 0 になることには特別な意味はありません)。そのため、返される内容をさまざまな nanotime 呼び出しの結果と比較する以外に、これを呼び出す必要はまったくありません。 nanotime は経過時間を追跡する、それだけです。

system.ctm システムクロックを取得します。 posix date コマンドを使用するか、システム設定でシステムの時刻設定を編集する場合、これは nanotime には影響しませんが、system.ctm が返す内容は変更されます。また、ctm は通常、nanotime よりもはるかに遅いです。 ctm には、1970 年 1 月 1 日の UTC 午前 0 時を表す、明確に定義された 0 もあります。

問題: 「現在時刻をナノ秒の精度でシステムクロックと一致させたい」は、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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はstackoverflow.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。