【填坑紀事】一次用System.nanoTime()填坑System.currentTimeMills()的實例記錄


       JDK提供了兩個方法,System.currentTimeMillis()和System.nanoTime(),這兩個方法都可以用來獲取表征當前時間的數值。但是如果不仔細辨別這兩個方法的差別和聯系,在使用當中也很容易出錯。筆者在前不久的工作當中使用System.currentTimeMillis()時就踩了一個大坑,后來在查明System.currentTimeMillis()和System.nanoTime()的特性后,才用System.nanoTime()來填了這個坑。本文,筆者就以自己的踩坑和填坑經歷來介紹一下這兩個方法。

 

 一、事故回顧

       文章的開頭,筆者先描述一下自己前不久工作當中碰到的一個bug,分析過程以及解決辦法。

  1、問題描述

        當手機按power鍵亮屏時,會調用人臉解鎖功能來解鎖手機。如果高頻率不停地按power鍵,人臉解鎖功能會被不停地調用,這樣會產生很多並發問題,導致一些不可預料的異常,同時在這么高頻率下,也沒有必要每次亮屏都調用人臉解鎖功能,需要過濾掉其中一部分調用。當時的處理辦法是,當上一次調用人臉解鎖功能的時候記錄當前時間點 long mLastUnlockTime = System.currentTimeMillis(); 當再次調用的時候,也記錄當前時間點 long nowUnlockTime = System.currentTimeMillis()。然后判斷這兩者的時間差 long durTime = nowUnlockTime - mLastUnlockTime,如果durTime<=300,表示距離上次調用不到300毫秒,本次不允許繼續調用;如果durTime>300,表示距離上一次調用已經超過300毫秒了,則允許這一次繼續調用,並把nowUnlockTime 的值賦給mLastUnlockTime,用於進行下一次的判斷。

       按照這個思路,就是防止300毫秒內連續調用兩次人臉解鎖功能,這種設計看起來似乎沒什么問題。但是這個設計在正常運行了幾個月后,測試人員提了一個bug:如果在系統設置中把時間往回調,那么人臉解鎖功能就失效了。

  2、原因分析

       當收到這個bug后,我百思不得其解,調個系統時間能把人臉解鎖功能給調失效了?我一度覺得這個現象很奇葩,不過作為一名老猿,我當然是去關鍵部分添加log分析原因了,最終定位到,是durTime出現問題了,居然出現了負數!!!這個時間差怎么會出現負數呢?仔細分析后才發現,這是System.currentTimeMills()的特性所致:該方法記錄的是系統時間距離1970年1月1日的毫秒數。當把時間往前調了,本次獲取的時間點nowUnlockTime 當然就小於上一次記錄的時間值了,那自然而然 durTime 就是負數了。

  3、解決辦法

       后來和某同事聊天,說起了這個聽起來似乎挺奇葩的現象,同事說推薦我去了解一下System.namoTime()這個方法。后來用 System.namoTime() 取代 System.currentTimeMillis() 后,問題就迎刃而解了。

 

二、System.currentTimeMillis()

  1、系統源碼說明

       咱們這里先看看系統類System.java中對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 <code>Date</code> 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();

  2、翻譯

       這里咱們翻譯一下系統源碼中的注釋:

1 以毫秒的方式返回當前時間。請注意,雖然返回值的時間單位是毫秒,但是這個值的粒度取決於底層操作系統並且可能粒度更大。例如,許多操作系統是以幾十毫秒為粒度測量時間的。 2 有關於“計算機時間”和協調世界時(UTC)之間的細微差別, 請查閱“Date”類。 3 @return 當前時間和1970年1月1日午夜之間的差值,以毫秒來測量。 4 @see java.util.Date

  3、補充說明

       (1)從源碼中可以看到,這個方式是一個native方法,該值由底層提供。

       (2)該方法可以用來計算當前日期,當前星期幾等,與Date的換算非常方便,JDK提供了相關的接口來換算。

       (3)通過該方法獲取的值的依據是當前系統的日期和時間,可以在系統設置中進行設置和修改。

 

三、System.nanoTime()

  1、系統源碼說明

       這里先看看系統類System.java中對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();

  2、翻譯

       這里先翻譯一下系統源碼中的注釋:

 1 返回正在運行的Java虛擬機的高分辨率時間源的當前值,以納秒計。 2 3 該方法可能僅僅用於測量已經逝去的時間,並且與任何其它系統或者掛鍾時間概念無關。該返回值表示從某個固定但任意的原點時間(可能在未來,所以值可能是負數)開始的納秒數。在一個java虛擬機實例中,所有該方法的調用都使用相同的原點;其它虛擬機實例很可能使用不同的源頭。 4 5 該方法提供了納秒級別的精度,但是不一定是納秒級分辨率(也就是該值改變的頻率)———— 除非這個分辨率至少和currentTimeMillis()一樣好,否則將不會做任何保證。 6 7 在跨越大於292年(2的63次方納秒)左右的連續調用中,這個差值將不能正確地計算已經過去的時間,因為數字溢出。 8 9 僅僅只有當在同一java虛擬機實例中獲取的兩個值之間的差值被計算時,返回值才有意義。 10 11 例如,去測量某代碼執行花費了多長時間: 12 long startTime = System.nanoTime(); 13 //...被測量的代碼... 14 long estimatedTime = System.nanoTime() - startTime; 15 16 要比較兩個nanoTime的值: 17 long t0 = System.nanoTime(); 18 ... 19 long t1 = System.nanoTime()。 20 因為數字溢出的可能性,您應該使用"t1 - t0 < 0",而不是"t1 < t0"(來判斷它們的大小,筆者注)。 21 @return 當前正在運行的java虛擬機的高精度時間資源值,以納秒為單位。 22 @since 1.5

 3、補充說明

    (1)該方法也是一個本地方法,返回值由底層提供。

    (2)如注釋中所說,該方法從java 1.5開始引入。 

    (3)該方法所基於的時間是隨機的,但在同一個JVM中,不同的地方使用的原點時間是一樣的。

 

四、兩者的區別與選擇

       前面對System.currentTimeMillis()和System.nanoTime()都分別從源碼注釋的角度進行了介紹,算是比較詳細了,這里再簡單終結一下,順便談一談工作中如何選擇:

    (1)System.nanoTime()的精確度更高一些,如今的硬件設備性能越來越好,如果要更精密計算執行某行代碼或者某塊代碼所消耗的時間,該方法會測量得更精確。開發者可以根據需要的精確度來選擇用哪一個方法。

    (2)單獨獲取System.nanoTime()沒有什么意義,因為該值是隨機的,無法表示當前的時間。如果要記錄當前時間點,用System.currentTimeMills()。

    (3)System.currentTimeMills()得到的值能夠和Date類方便地轉換,jdk提供了多個接口來實現;但是System.nanoTime()則不行。

    (4) System.currentTimeMills()的值是基於系統時間的,可以人為地進行修改;而System.nanoTime()則不能,所以如文章開頭筆者碰到的問題一樣,如果需要根據時間差來過濾某些頻繁的操作,用System.nanoTime()會比較合適。

 

五、結語

       程序中往往某一行簡單的代碼,都包含了很多的知識點,真正推敲起來,似乎沒有簡單的東西。計算機世界浩如煙海,知識博大精深,我等只能望洋興嘆啊!!!本文的內容是基於自己平時的工作實踐,然后做的總結,如果有錯誤的地方或者不妥的地方,還請不吝賜教,謝謝!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM