<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          拋棄性能不佳的System.currentTimeMillis(),手擼一個低開銷獲取時間戳工具

          共 9366字,需瀏覽 19分鐘

           ·

          2021-09-12 11:52

          你知道的越多,不知道的就越多,業(yè)余的像一棵小草!

          你來,我們一起精進!你不來,我和你的競爭對手一起精進!

          編輯:業(yè)余草

          推薦:https://www.xttblog.com/?p=5277

          大家好,我是業(yè)余草,這是我的第 447 篇原創(chuàng)!

          你或許聽說過,在 Java 中調(diào)用 System.currentTimeMillis() 會有一些性能開銷,在某些場景下,System.nanoTime() 更具優(yōu)勢!

          比如,測試方法的耗時時間:

          public void save(){
              long start = System.currentTimeMillis();
              // doSomething() ...
              System.out.println(System.currentTimeMillis() - start);
          }

          這里建議你System.currentTimeMillis()改為System.nanoTime()

          public void save(){
              long start = System.nanoTime();
              // doSomething() ...
              System.out.println(System.nanoTime() - start);
          }

          原因我們下面慢慢展開。

          昨天群里還有人說,可以使用 StopWatch。豈不知,StopWatch 背后也是System.currentTimeMillis()

          public void save(){
              StopWatch stopWatch = new StopWatch();
              stopWatch.start();
              // doSomething() ...
              stopWatch.stop();
              System.out.println(stopWatch.prettyPrint());
          }
          StopWatch底層時間獲取

          System.currentTimeMillis() 的缺點

          System.currentTimeMillis()返回的是毫秒數(shù),System.nanoTime()返回的是納秒數(shù)。如果方法跑的比較快,毫秒的測試就更不準確了。

          1000 皮秒 = 1納秒 
          1000000 皮秒 = 1微秒
          1000000000 皮秒 = 1毫秒
          1000000000000 皮秒 = 1秒

          1s = 1000 ms 毫秒
          1ms = 1000000 ns 納秒

          更何況,currentTimeMillis依賴底層操作系統(tǒng),nanoTime則是有 JVM 維護。

          展開來說就是,我們在 Java 中獲取時間戳的方法是System.currentTimeMillis()返回的是毫秒級的時間戳。查看源碼注釋,寫的比較清楚,雖然該方法返回的是毫秒級的時間戳,但精度取決于操作系統(tǒng),很多操作系統(tǒng)返回的精度是 10 毫秒。

          /**
          * Returns the current time in milliseconds.  Note that
          * while the unit of time of the return value is a millisecond,
          * the granularity of the value depends on the underlying
          * operating system and may be larger.  For example, many
          * operating systems measure time in units of tens of
          * milliseconds.
          *
          * <p> See the description of the class <code>Date</code> for
          * a discussion of slight discrepancies that may arise between
          * "computer time" and coordinated universal time (UTC).
          *
          @return  the difference, measured in milliseconds, between
          *          the current time and midnight, January 1, 1970 UTC.
          @see     java.util.Date
          */

          public static native long currentTimeMillis();

          以 HotSpot 源碼為例,源碼在 hotspot/src/os/linux/vm/os_linux.cpp 文件中,有一個javaTimeMillis()方法,這就是System.currentTimeMillis()的 native 實現(xiàn)。

          jlong os::javaTimeMillis() {
            timeval time;
            int status = gettimeofday(&time, NULL);
            assert(status != -1"linux error");
            return jlong(time.tv_sec) * 1000  +  jlong(time.tv_usec / 1000);
          }

          這是 C++ 寫的,我也看不懂。我們直接拿老外的研究來學(xué)習(xí):http://pzemtsov.github.io/2017/07/23/the-slow-currenttimemillis.html

          總結(jié)起來原因是System.currentTimeMillis調(diào)用了gettimeofday()

          • 調(diào)用gettimeofday()需要從用戶態(tài)切換到內(nèi)核態(tài);
          • gettimeofday()的表現(xiàn)受Linux系統(tǒng)的計時器(時鐘源)影響,在 HPET 計時器下性能尤其差;
          • 系統(tǒng)只有一個全局時鐘源,高并發(fā)或頻繁訪問會造成嚴重的爭用。

          我們測試一下System.currentTimeMillis()在不同線程下的性能,這里使用中間件常用的JHM來測試,測試 1 到 128 線程下獲取 1000 萬次時間戳需要的時間分別是多少,這里給出在我的電腦上的測試數(shù)據(jù):

          System.currentTimeMillis()性能測試

          還有一個問題就是,currentTimeMillis 獲取的是系統(tǒng)時間源。因此,系統(tǒng)時間變更,或者系統(tǒng)自動進行了時間同步,計算兩次獲取的差值,可能是負數(shù)。

          另外System.currentTimeMillis()返回自紀元(即自 1970 年 1 月 1 日 UTC 午夜以來的毫秒數(shù))。如果你的系統(tǒng)設(shè)置的時間小于這個時間,那么 currentTimeMillis 的取值也可能是負數(shù)。當然幾乎沒人會這么設(shè)置時間,除非是黑客。

          設(shè)置系統(tǒng)時間

          小總結(jié):使用System.currentTimeMillis()要注意精度、性能開銷、時間同步影響準確性、時間不安全可能是負數(shù)、高并發(fā)場景隨機數(shù)不均衡等問題。

          System.nanoTime() 的缺點

          System.nanoTime()是 JDK 1.5 才推出的,因此 1.5 之前的辦法無法使用。

          第二,源碼注釋中描述它是安全的。但在老外的使用過程中發(fā)現(xiàn),它有時候也不安全,返回的也可能是負數(shù)。

          另外官方建議,可以使用它來測量 elapsed time,不能用來當作 wall-clock time 或 system time。

          ?

          This method can only be used to measure elapsed time and is not related to any other notion of system or wall-clock time.

          ?

          網(wǎng)上還暴露出,多核處理器不同核心的啟動時間可能不完全一致,這樣可能會造成System.nanoTime()計時錯誤。參考:https://stackoverflow.com/questions/510462/is-system-nanotime-completely-useless

          手擼一個 currentTimeMillis

          先定義一個工具類:TimeUtil。

          /**
           * 弱精度的計時器,考慮性能不使用同步策略。
           */

          public class TimeUtil {
              private static long CURRENT_TIME = System.currentTimeMillis();

              public static final long currentTimeMillis() {
                  return CURRENT_TIME;
              }

              public static final void update() {
                  CURRENT_TIME = System.currentTimeMillis();
              }

          }

          然后起一個定時器,定時更新維護時間。

          import java.util.Timer;
          import java.util.TimerTask;

          public class TimerServer {
              private static final TimerServer INSTANCE = new TimerServer();
              private final Timer timer;
              
              private TimerServer(){
                  timer = new Timer("業(yè)余草Timer"true);
                  timer.schedule(updateTime(), 0L20L);
              }

              // 系統(tǒng)時間定時更新任務(wù)
              private TimerTask updateTime() {
                  return new TimerTask() {
                      @Override
                      public void run() {
                          TimeUtil.update();
                      }
                  };
              }

              public static final TimerServer getInstance() {
                  return INSTANCE;
              }
          }

          或者直接用一個 TimeUtil 類搞定。

          public final class TimeUtil {

              private static volatile long currentTimeMillis;

              static {
                  currentTimeMillis = System.currentTimeMillis();
                  Thread daemon = new Thread(new Runnable() {
                      @Override
                      public void run() {
                          while (true) {
                              currentTimeMillis = System.currentTimeMillis();
                              try {
                                  TimeUnit.MILLISECONDS.sleep(1);
                              } catch (Throwable e) {

                              }
                          }
                      }
                  });
                  daemon.setDaemon(true);
                  daemon.setName("業(yè)余草-time-tick-thread");
                  daemon.start();
              }

              public static long currentTimeMillis() {
                  return currentTimeMillis;
              }
          }

          這樣做的好處就是,在高并發(fā)場景下,對時間要求較高的場景,則可以自己維護系統(tǒng)時鐘。

          經(jīng)過 JMH 測試對比(測試代碼可以加我微信:codedq,免費獲取),我們手擼的 TimeUtil 在 1-128 線程下的性能表現(xiàn)非常強勁,比系統(tǒng)自帶的System.currentTimeMillis()高出近 876 倍。

          低開銷獲取時間戳

          比如:阿里的 Sentinel,Cobar等。Twitter 的 Snowflake(很多人在實現(xiàn) Snowflake 時,采用了 System.currentTimeMillis())。

          總結(jié)

          雖然緩存時間戳性能能提升很多,但這也僅限于非常高的并發(fā)系統(tǒng)中,一般比較適用于高并發(fā)的中間件,如果一般的系統(tǒng)來做這個優(yōu)化,效果并不明顯。性能優(yōu)化還是要抓住主要矛盾,解決瓶頸,切忌不可過度優(yōu)化。

          參考資料

          • https://en.wikipedia.org/wiki/High_Precision_Event_Timer
          • https://en.wikipedia.org/wiki/Time_Stamp_Counter
          • http://pzemtsov.github.io/2017/07/23/the-slow-currenttimemillis.html
          • https://stackoverflow.com/questions/510462/is-system-nanotime-completely-useless

          瀏覽 156
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人网站在线看 | 无码不卡免费视频 | 无人无码深夜久久 | 亚洲性爱在线播放 | 在线观看高清无码 |