找回密码
 立即注册
首页 业界区 安全 [Java/时间] 核心源码精讲:System.nanoTime()/currentT ...

[Java/时间] 核心源码精讲:System.nanoTime()/currentTimeMills()

圄旧剖 昨天 18:14
1 缘起:观测数据处理程序的耗时


  • 某项目中需要观测数据处理程序的耗时情况,以便分析各环节的性能。
  • 然而,JDK提供了2个获取当前时间的方法: System.currentTimeMillis()(获取来自操作系统的毫秒级时间戳) 和System.nanoTime() (获取纳秒级时间点)
这两个方法都可以用来获取表征当前时间的数值。
但如果不仔细辨别这两个方法的差别和联系,在使用当中很容易出错。
  1. //同一时刻执行:
  2. System.nanoTime() + " # " + System.currentTimeMillis()
  3. //out: 88569108530100 # 1756542014118
复制代码
88 5691 0853 0100 (14位,纳秒级的原点时间) # 1 7565 4201 4118 (13位,UTC+0的毫秒级时间戳)
二者为何差距如此大?
显然,后者才是 UTC 时间戳;前者显然不是,但它的意义和用途是什么呢?
2 源码分析

System.currentTimeMillis()


  • 源码说明
  1. /**
  2.      * Returns the current time in milliseconds.  Note that
  3.      * while the unit of time of the return value is a millisecond,
  4.      * the granularity of the value depends on the underlying
  5.      * operating system and may be larger.  For example, many
  6.      * operating systems measure time in units of tens of
  7.      * milliseconds.
  8.      *
  9.      * <p> See the description of the class Date for
  10.      * a discussion of slight discrepancies that may arise between
  11.      * "computer time" and coordinated universal time (UTC).
  12.      *
  13.      * @return  the difference, measured in milliseconds, between
  14.      *          the current time and midnight, January 1, 1970 UTC.
  15.      * @see     java.util.Date
  16.      */
  17.     public static native long currentTimeMillis();
复制代码
译文:
  1. 以毫秒的方式返回当前时间。请注意,虽然返回值的时间单位是毫秒,但是这个值的粒度取决于【底层操作系统】,并且可能【粒度】更大。例如,许多操作系统是以【几十毫秒】为粒度测量时间的。
  2. 有关于“计算机时间”和协调世界时(UTC)之间的细微差别, 请查阅“Date”类。
  3. @return 当前时间和1970年1月1日午夜之间的差值,以毫秒来测量。
  4. @see java.util.Date
复制代码

  • 解读
(1)从源码中可以看到,这个方式是一个native方法,该值由【底层的操作系统】提供。
(2)该方法可以用来计算当前日期,当前星期几等,与Date的换算非常方便,JDK提供了相关的接口来换算。
(3)通过该方法获取的值的依据是当前系统的日期和时间,可以在【操作系统】设置中进行【设置】和【修改】。
所以,若【操作系统】的时间/时钟没有及时校准,其时间戳也是可能会出错的。
System.nanoTime()


  • 官方源码
  1. /**
  2.      * Returns the current value of the running Java Virtual Machine's
  3.      * high-resolution time source, in nanoseconds.
  4.      *
  5.      * <p>This method can only be used to measure elapsed time and is
  6.      * not related to any other notion of system or wall-clock time.
  7.      * The value returned represents nanoseconds since some fixed but
  8.      * arbitrary <i>origin</i> time (perhaps in the future, so values
  9.      * may be negative).  The same origin is used by all invocations of
  10.      * this method in an instance of a Java virtual machine; other
  11.      * virtual machine instances are likely to use a different origin.
  12.      *
  13.      * <p>This method provides nanosecond precision, but not necessarily
  14.      * nanosecond resolution (that is, how frequently the value changes)
  15.      * - no guarantees are made except that the resolution is at least as
  16.      * good as that of {@link #currentTimeMillis()}.
  17.      *
  18.      * <p>Differences in successive calls that span greater than
  19.      * approximately 292 years (2<sup>63</sup> nanoseconds) will not
  20.      * correctly compute elapsed time due to numerical overflow.
  21.      *
  22.      * <p>The values returned by this method become meaningful only when
  23.      * the difference between two such values, obtained within the same
  24.      * instance of a Java virtual machine, is computed.
  25.      *
  26.      * <p> For example, to measure how long some code takes to execute:
  27.      *  <pre> {@code
  28.      * long startTime = System.nanoTime();
  29.      * // ... the code being measured ...
  30.      * long estimatedTime = System.nanoTime() - startTime;}</pre>
  31.      *
  32.      * <p>To compare two nanoTime values
  33.      *  <pre> {@code
  34.      * long t0 = System.nanoTime();
  35.      * ...
  36.      * long t1 = System.nanoTime();}</pre>
  37.      *
  38.      * one should use {@code t1 - t0 < 0}, not {@code t1 < t0},
  39.      * because of the possibility of numerical overflow.
  40.      *
  41.      * @return the current value of the running Java Virtual Machine's
  42.      *         high-resolution time source, in nanoseconds
  43.      * @since 1.5
  44.      */
  45.     public static native long nanoTime();
复制代码
译文
  1. 返回正在运行的Java虚拟机的【高分辨率时间源】的当前值,以【纳秒】计。
  2. 该方法可能仅仅用于测量已经逝去的时间,并且与任何【其它系统】或者【挂钟时间】概念无关。该返回值表示从某个固定但任意的【原点时间】(可能在未来,所以值可能是负数)开始的纳秒数。在一个java虚拟机实例中,所有该方法的调用都使用相同的原点;其它虚拟机实例很可能使用不同的源头。
  3. 该方法提供了纳秒级别的精度,但是不一定是纳秒级分辨率(也就是该值改变的频率)———— 除非这个分辨率至少和currentTimeMillis()一样好,否则将不会做任何保证。
  4. 在跨越大于292年(2的63次方纳秒)左右的连续调用中,这个差值将不能正确地计算已经过去的时间,因为数字溢出。
  5. 仅仅只有当在同一java虚拟机实例中获取的两个值之间的差值被计算时,返回值才有意义。
  6. 例如,去测量某代码执行花费了多长时间:
  7. long startTime = System.nanoTime();
  8. //...被测量的代码...
  9. long estimatedTime = System.nanoTime() - startTime;
  10. 要比较两个nanoTime的值:
  11. long t0 = System.nanoTime();
  12. ...
  13. long t1 = System.nanoTime()。
  14. 因为数字溢出的可能性,您应该使用"t1 - t0 < 0",而不是"t1 < t0"(来判断它们的大小,笔者注)。
  15. @return 当前正在运行的java虚拟机的高精度时间资源值,以纳秒为单位。
  16. @since 1.5
复制代码

  • 解读
(1)该方法也是一个本地方法,返回值由【底层的操作系统】提供。
(2)如注释中所说,该方法从java 1.5开始引入。
(3)该方法所基于的时间是【随机】的,但在同一个JVM中,【不同的地方】使用的【原点时间】是一样的。
(4)与操作系统的【时钟时间】无关 => 即 与 UTC 时间戳无关。
多用于:观测纳秒级的程序耗时场景。
3 分析结论与最佳实践

前面对System.currentTimeMillis()和System.nanoTime()都分别从源码注释的角度进行了介绍,算是比较详细了,这里再简单终结一下,顺便谈一谈工作中如何选择:


  • System.nanoTime()的精确度更高一些,如今的硬件设备性能越来越好,如果要更精密计算执行某行代码或者某块代码所消耗的时间,该方法会测量得更精确。开发者可以根据需要的精确度来选择用哪一个方法。
  • 单独获取System.nanoTime()没有什么意义,因为该值是随机的,无法表示当前的时间。
如果要记录当前时间点,用System.currentTimeMills()。


  • System.currentTimeMills()得到的值能够和Date类方便地转换,jdk提供了多个接口来实现;但是System.nanoTime()则不行。
  • System.currentTimeMills()的值是基于操作系统的时钟时间的、可人为修改;而System.nanoTime()则不能(与操作系统的时钟无关、与UTC时间戳无关)。
  • 结合 System.currentTimeMills() + System.nanoTime(),计算时间戳
仅供参考,逻辑不一定绝对严谨。
  1. Long nanoTime = System.nanoTime();
  2. Long timestamp = nanoTimeToMillisTimestamp(nanoTime);
  3. System.out.println(String.format("nanoTime: %dns, ts : %dms", nanoTime, timestamp));
  4. String.format("nanoTime: %dns, ts : %dms", nanoTime, timestamp)
复制代码
[out]nanoTime: 100181142845700ns, ts : 1756553627414ms
  1.     /**
  2.      * 纳秒数转毫秒级时间戳
  3.      * @param targetNanoTime
  4.      *   eg: long targetNanoTime = System.nanoTime(); // 获取当前时间的 nano time,例如 : 87277522042200
  5.      * @return
  6.      */
  7.     public static long nanoTimeToMillisTimestamp(long targetNanoTime){
  8.         //待转换的纳秒时间(targetNanoTime)
  9.         //long targetNanoTime = System.nanoTime(); // 获取当前时间的 nano time,例如 : 87277522042200
  10.         //获取当前的毫秒数(currentTimeMillis) 和 纳秒数(currentNanoTime)
  11.         long currentTimeMillis = System.currentTimeMillis();//例如: 1733985453227
  12.         long currentNanoTime = System.nanoTime();//例如 : 87277522044200
  13.         //计算 currentNanoTime 与 targetNanoTime 的差值(nanoTimeDiff)
  14.         long nanoTimeDiff = currentNanoTime - targetNanoTime;//例如: 2000
  15.         // 利用 currentNanoTime 和 nanoTimeDiff ,向前推算出 targetNanoTime当时对应的毫秒级时间戳
  16.         long targetMillisTime = currentTimeMillis - ( nanoTimeDiff / 1000000L);//13位的毫秒级时间戳,例如: 1733985453227
  17.         //Date date = new Date(targetMillisTime); // 创建日期对象
  18.         //System.out.println("date : " + date.toString());//例如: date : Thu Dec 12 14:37:33 CST 2024
  19.         return targetMillisTime;
  20.     }
复制代码
X 参考文献


  • 【填坑往事】一次用System.nanoTime()填坑System.currentTimeMills()的实例记录 - 博客园
    本文作者:        千千寰宇   
    本文链接:         https://www.cnblogs.com/johnnyzen   
    关于博文:评论和私信会在第一时间回复,或直接私信我。   
    版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA     许可协议。转载请注明出处!
    日常交流:大数据与软件开发-QQ交流群: 774386015        【入群二维码】参见左下角。您的支持、鼓励是博主技术写作的重要动力!   

来源:豆瓜网用户自行投稿发布,如果侵权,请联系站长删除

相关推荐

您需要登录后才可以回帖 登录 | 立即注册