Maison  >  Article  >  Pourquoi le temps écoulé calculé sur une grande fenêtre de temps diffère-t-il de plus de 100 millisecondes entre System.currentTimeMillis et System.nanoTime

Pourquoi le temps écoulé calculé sur une grande fenêtre de temps diffère-t-il de plus de 100 millisecondes entre System.currentTimeMillis et System.nanoTime

王林
王林avant
2024-02-09 10:24:08816parcourir

L'éditeur de PHP Apple expliquera pourquoi le temps écoulé calculé sur une grande fenêtre de temps présente une différence allant jusqu'à 100+ millisecondes entre System.currentTimeMillis et System.nanoTime. Ce problème implique le mécanisme sous-jacent du système informatique et la méthode de planification du système d'exploitation. Lors du calcul du temps écoulé, System.currentTimeMillis utilise l'heure de l'horloge murale fournie par le système d'exploitation, tandis que System.nanoTime utilise les cycles d'horloge du processeur. En raison du mécanisme de planification du système d'exploitation et de la nature asynchrone de l'horloge matérielle, ces deux méthodes de synchronisation entraîneront des différences horaires. Les raisons spécifiques et les solutions seront présentées en détail dans les articles suivants.

Contenu de la question

J'essaie de créer un ksuid qui peut effectuer un tri par précession en microsecondes dans la même machine virtuelle, puisque currenttimemillis ne donne qu'une précision en millisecondes, pensez à utiliser currenttimemillis() et nanotime() ensemble pour collecter le premier instant. La différence nanotime est calculé comme le nombre de microsecondes écoulées depuis le premier instant et la valeur commençant au deuxième instant. Utilisez le temps écoulé calculé à partir du nanotime et ajoutez-le aux millisecondes instantanées initiales afin que nous puissions calculer l'époque avec une précision de la microseconde à tout instant après le démarrage de la JVM (le tout dans la même JVM).

//Collection statique des nanotimes et currentmillis initiaux 最终长 inittimenanos = system.nanotime();最终长 inittimemillis = system.currenttimemillis();

//À partir de là, lorsque l'heure actuelle avec une précision de la micro/nanoseconde est requise, calculez le temps écoulé à partir du nanotime et ajoutez les millisecondes écoulées au milliseconde initial, et le temps écoulé restant donne le temps avec une précision de la microseconde

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

// Augmentation monotone du nombre de millisecondes d'horodatage depuis le démarrage de jvm (époque, nous ne pouvons pas l'appeler époque) final 长计算当前时间millis = inittimemillis + elapsedmillisfromnanotime; final long nanosprecision = elapsednanos % 1000000; //Précision temporelle supplémentaire grâce au nanotime

Envisagez d'utiliser ces valeurs pour générer un ksuid croissant de manière monotone qui se rapproche de l'heure actuelle mais est garanti d'être trié en fonction de l'heure de création au sein de la même machine virtuelle Java, car currenttimemillis ne fournit pas de garantie d'horodatage croissant monotone, envisagez d'utiliser cette approche pour générer approximativement Horodatage croissant de manière monotone de l'horodatage réel (peut varier de quelques millisecondes à moins que des ajustements de seconde intercalaire ne soient apportés à l'heure de l'époque). Le temps écoulé calculé à l'aide de l'époque et du nanotemps devait différer de quelques millisecondes, mais la différence réelle variait très fréquemment, et lorsque j'ai exécuté le test ci-dessous pendant 48 heures, j'ai observé une différence allant jusqu'à 150 millisecondes entre les deux. Dans la plupart des cas, le temps écoulé calculé à l'aide du nanotime est supérieur au temps écoulé calculé à l'aide de currenttimemillis, et les temps observés varient de -2 millisecondes à +150 millisecondes.

final long elapsedmillis = system.currenttimemillis() - inittimemillis; //Temps écoulé en millisecondes depuis system.currenttimemillis() 最终双方差millis = elapsedmillisfromnanotime - elapsedmillis; //Variation du temps écoulé

Est-ce que quelque chose me manque à propos des garanties de timing JVM ? La façon de calculer les horodatages approximatifs croissants de manière monotone est-elle erronée ? (Ceci n'est pas utilisé comme époque réelle dans le système, uniquement pour générer l'uuid qui représente également l'instant d'horodatage approximatif au sein du même jvm).

//Cours test

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(".");
        }
    }
}

Pourquoi la variance du temps écoulé continue-t-elle de changer ? Quelle est la variance maximale à laquelle nous pouvons nous attendre si la JVM fonctionne en continu pendant un an (n'importe quelle JVM garantit une limite supérieure ou inférieure à ce sujet, testée avec Mac et Windows, Mac a donné une lente augmentation de la variance, Windows était beaucoup plus rapide) ?

Je m'attendrais à ce que le temps écoulé varie de moins de 10 millisecondes et que les changements se produisent moins fréquemment. Mais l'observation réelle est que la variance ne cesse de changer, fluctuant de haut en bas, avec jusqu'à 150 millisecondes observées en 48 heures

Solution

Une explication est le maculage du temps basé sur ntp. Plus généralement, le nanotime et le ctm mesurent des choses complètement différentes et on ne peut pas les mélanger.

nanotime a un point 0 arbitraire (obtenir un nanotime de 0 n'a aucune signification particulière), il n'est donc pas nécessaire de l'appeler du tout autre que de comparer ce qu'il renvoie avec les résultats de différents appels nanotime. le nanotime suit le temps écoulé, c'est tout.

system.ctm obtient l'horloge système. Si vous utilisez posix date 命令或在系统设置中编辑系统的时间设置,则不会影响 nanotime,但会更改 system.ctm, ce qui est renvoyé. ctm est également généralement beaucoup plus lent que le nanotime. ctm a également un 0 bien défini - représentant minuit UTC le 1er janvier 1970.

Question : "Je veux que l'heure actuelle corresponde à l'horloge système avec une précision de la nanoseconde" n'est pas possible sur 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 类能够以纳秒为单位。

Idée intelligente. Mais ce n'est pas réaliste. Comme mentionné ci-dessus, currenttimemillisnanotime 不相关。最重要的是,currenttimemillis 的结果可能会因多种原因而有所不同。同时,nanotime 计数可能会随着主机的每次启动而变化,并且肯定不会对应于其他计算机上进行的任何 nanotime appelle.

Oui, vous ignorez le fait que la spécification Java ne donne aucune des garanties que vous semblez supposer.

La seule garantie dans

javadoc est que nanotime 是“高分辨率”,至少与 currenttimemillis est aussi bon. Et il n'y a aucune garantie quand on peut s'attendre à la fin de 292.

En même temps, currenttimemillis est une cible mouvante et peut changer à tout moment, avancer ou reculer.

C’est imprévisible.

La spécification Java ne donne aucune garantie de ce type.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer