<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的性能真有如此不堪嗎?

          共 4356字,需瀏覽 9分鐘

           ·

          2020-11-05 19:35

          作者:圍軍兒

          來源:https://juejin.im/post/6887743425437925383

          疑惑,System.currentTimeMillis真有性能問題?

          最近我在研究一款中間件的源代碼時,發(fā)現(xiàn)它獲取當(dāng)前時間不是通過System.currentTimeMillis,而是通過自定義的System.currentTimeMillis的緩存類(見下方),難道System.currentTimeMillis的性能如此不堪嗎?竟然要通過自定義的緩存時鐘取而代之?

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

          public?class?TimeUtil?{
          ????//當(dāng)前毫秒數(shù)的緩存
          ????private?static?volatile?long?CURRENT_TIME?=?System.currentTimeMillis();

          ????public?static?final?long?currentTimeMillis()?{?return?CURRENT_TIME;?}
          ????public?static?final?long?currentTimeNanos()?{?return?System.nanoTime();?}
          ?//更新緩存
          ????public?static?final?void?update()?{?CURRENT_TIME?=?System.currentTimeMillis();?}

          }
          //使用定時任務(wù)調(diào)度線程池,定期(每1s)調(diào)用update方法更新緩存時鐘
          heartbeatScheduler.scheduleAtFixedRate(processorCheck(),?0L,?1000,?TimeUnit.MILLISECONDS);

          為了跟緊時代潮流,跟上性能優(yōu)化“大師”們的步伐,我趕緊上網(wǎng)搜了一下“currentTimeMillis性能”,結(jié)果10個搜索結(jié)果里面有9個是關(guān)于system.currentTimeMillis性能問題的:

          點開一看,這個說System.currentTimeMillis 比 new一個普通對象耗時還要高100倍左右,那個又拿出測試記錄說System.currentTimeMillis并發(fā)情況下耗時比單線程調(diào)用高250倍

          思索,System.currentTimeMillis有什么性能問題

          看到這里,我恨不得馬上打開IDEA,把代碼里所有System.currentTimeMillis都給換掉,但是作為一個嚴(yán)謹(jǐn)?shù)某绦騿T,怎么能隨波逐流,人云亦云呢?于是我仔細(xì)地拜讀了這些文章,總結(jié)了他們的觀點:

          • System.currentTimeMillis要訪問系統(tǒng)時鐘,這屬于臨界區(qū)資源,并發(fā)情況下必然導(dǎo)致多線程的爭用
          • System.currentTimeMillis()之所以慢是因為去跟系統(tǒng)打了一次交道
          • 有測試記錄,并發(fā)耗時就是比單線程高250倍!

          但我細(xì)品一番,發(fā)現(xiàn)這些觀點充滿了漏洞:

          1. System.currentTimeMillis 確實要訪問系統(tǒng)時鐘,準(zhǔn)確的說,是讀取墻上時間(xtime),xtime是Linux系統(tǒng)給用戶空間用來獲取當(dāng)前時間的,內(nèi)核自己基本不會使用,只是維護更新。而且讀寫xtime使用的是Linux內(nèi)核中的順序鎖,而非互斥鎖,讀線程間是互不影響的

            大家可以把順序鎖當(dāng)成是解決了“ABA問題”的CompareAndSwap鎖。對于一個臨界區(qū)資源(這里是xtime),有一個操作序列號,寫操作會使序列號+1,讀操作則不會。

            寫操作:CAS使序列號+1

            讀操作:先獲取序列號,讀取數(shù)據(jù),再獲取一次序列號,前后兩次獲取的序列號相同,則證明進行讀操作時沒有寫操作干擾,那么這次讀是有效的,返回數(shù)據(jù),否則說明讀的時侯可能數(shù)據(jù)被更改了,這次讀無效,重新做讀操作。

            大家可能有個疑問:讀xtime的時候數(shù)據(jù)可能被更改嗎?難度讀操作不是原子性的嗎?這是因為xtime是64位的,對于32位機器是需要分兩次讀的,而64位機器不會產(chǎn)生這個并發(fā)的問題。

          2. 跟系統(tǒng)打了一次交道,確實,用戶進程必須進入內(nèi)核態(tài)才能訪問系統(tǒng)資源,但是,new一個對象,分配內(nèi)存也屬于系統(tǒng)調(diào)用,也要進內(nèi)核態(tài)跟系統(tǒng)打交道,難道只是讀一下系統(tǒng)的墻上時間,會比移動內(nèi)存指針,初始化內(nèi)存的耗時還要高100倍嗎?匪夷所思

          3. 至于所謂的測試記錄,給大家看一下他的測試代碼:


            這個測試代碼的問題在于閉鎖endLatch.countDown的耗時也被算進總體耗時了,閉鎖是基于CAS實現(xiàn)的,在當(dāng)前這樣的計算密集型場景下,大量線程一擁而上,幾乎都會因CAS失敗而被掛起,大量線程掛起、排隊、放下的耗時可不是小數(shù)目。其次使用這種方法(執(zhí)行開始到執(zhí)行完畢)來對比并發(fā)和單線程的調(diào)用耗時也有問題,單線程怎么和多線程比總的執(zhí)行時間?能比的應(yīng)該是每次調(diào)用的耗時之和才對(見下)

            long?begin?=?System.nanoTime();
            //單次調(diào)用System.currrentTimeMillis()
            long?end?=?System.nanoTime();
            sum?+=?end?-?begin;

            記錄每次調(diào)用的總耗時,這種方法雖然會把System.nanoTime()也算進總耗時里,但因為不論并發(fā)測試還是單線程測試都會記錄System.nanoTime(),不會導(dǎo)致測試的不公平

          數(shù)據(jù)說話,System.currentTimeMillis的性能沒有問題

          通過改進測試代碼(測試代碼見文末),并添加了優(yōu)化“大師”們的緩存時鐘做對比,我得到了以下數(shù)據(jù):

          次數(shù)\耗時\場景單線程System單線程緩存時鐘200線程System200線程緩存時鐘
          1w3.682 ms42.844 ms0.583 ms0.444 ms
          10w6.780 ms35.837 ms3.379 ms3.066 ms
          100w30.764 ms70.917 ms36.416 ms27.906 ms
          1000w263.287 ms427.319 ms355.452 ms261.360 ms

          System代表 System.currentTimeMillis

          緩存時鐘代表 使用靜態(tài)成員變量做System.currentTimeMillis緩存的時鐘類

          200線程-Tomcat的默認(rèn)線程數(shù)

          使用JMH(Java基準(zhǔn)測試框架)的測試結(jié)果

          測試次數(shù)\平均耗時\場景System緩存時鐘
          1w0.368 ± 0.667 微秒/次0.578 ± 1.039 微秒/次

          JMH按照推薦使用了雙倍CPU的線程數(shù)(8線程),統(tǒng)計的是平均時間,測試代碼見文末

          測試結(jié)果分析

          可以看到System.currentTimeMillis并發(fā)性能并不算差,在次數(shù)較少(短期并發(fā)調(diào)用)的情況下甚至比單線程要強很多,而在單線程調(diào)用時效率也要比緩存時鐘要高一倍左右。實際環(huán)境中幾乎是達(dá)不到上述測試中的多線程長時間并發(fā)調(diào)用System.currentTimeMillis這樣的情況的,因而我認(rèn)為沒有必要對System.currentTimeMillis做所謂的“優(yōu)化”

          這里沒有做“new一個對象”的測試,是因為并不是代碼里寫了new Object(),JVM就會真的會給你在堆內(nèi)存里new一個對象。這是JVM的一個編譯優(yōu)化——逃逸分析:先分析要創(chuàng)建的對象的作用域,如果這個對象只在一個method里有效(局部變量對象),則屬于未 方法逃逸,不去實際創(chuàng)建對象,而是你在method里調(diào)了對象的哪個方法,就把這個方法的代碼塊內(nèi)聯(lián)進來。只在線程內(nèi)有效則屬于未 線程逃逸,會創(chuàng)建對象,但會自動消除我們做的無用的同步措施。

          最后

          紙上得來終覺淺,絕知此事要躬行

          想要學(xué)習(xí)JMH,請跟著GitHub官方文檔走,別人的博客可能跑不通就搬上去了,筆者也是剛剛踩過了這個坑

          最后奉上我的測試代碼

          測試代碼:

          public?class?CurrentTimeMillisTest?{

          ????public?static?void?main(String[]?args)?{
          ????????int?num?=?10000000;
          ????????System.out.print("單線程"+num+"次System.currentTimeMillis調(diào)用總耗時:????");
          ????????System.out.println(singleThreadTest(()?->?{
          ????????????long?l?=?System.currentTimeMillis();
          ????????},num));
          ????????System.out.print("單線程"+num+"次CacheClock.currentTimeMillis調(diào)用總耗時:");
          ????????System.out.println(singleThreadTest(()?->?{
          ????????????long?l?=?CacheClock.currentTimeMillis();
          ????????},num));
          ????????System.out.print("并發(fā)"+num+"次System.currentTimeMillis調(diào)用總耗時:??????");
          ????????System.out.println(concurrentTest(()?->?{
          ????????????long?l?=?System.currentTimeMillis();
          ????????},num));
          ????????System.out.print("并發(fā)"+num+"次CacheClock.currentTimeMillis調(diào)用總耗時:??");
          ????????System.out.println(concurrentTest(()?->?{
          ????????????long?l?=?CacheClock.currentTimeMillis();
          ????????},num));
          ????}



          ????/**
          ?????*?單線程測試
          ?????*?@return
          ?????*/

          ????private?static?long?singleThreadTest(Runnable?runnable,int?num)?{
          ????????long?sum?=?0;
          ????????for?(int?i?=?0;?i?????????????long?begin?=?System.nanoTime();
          ????????????runnable.run();
          ????????????long?end?=?System.nanoTime();
          ????????????sum?+=?end?-?begin;
          ????????}
          ????????return?sum;
          ????}

          ????/**
          ?????*?并發(fā)測試
          ?????*?@return
          ?????*/

          ????private?static?long?concurrentTest(Runnable?runnable,int?num)?{
          ????????ThreadPoolExecutor?threadPoolExecutor?=?new?ThreadPoolExecutor(200,200,60,?TimeUnit.SECONDS,new?LinkedBlockingQueue<>(num));
          ????????long[]?sum?=?new?long[]{0};
          ????????//閉鎖基于CAS實現(xiàn),并不適合當(dāng)前的計算密集型場景,可能導(dǎo)致等待時間較長
          ????????CountDownLatch?countDownLatch?=?new?CountDownLatch(num);
          ????????for?(int?i?=?0;?i?????????????threadPoolExecutor.submit(()?->?{
          ????????????????long?begin?=?System.nanoTime();
          ????????????????runnable.run();
          ????????????????long?end?=?System.nanoTime();
          ????????????????//計算復(fù)雜型場景更適合使用悲觀鎖
          ????????????????synchronized(CurrentTimeMillisTest.class)?{
          ????????????????????sum[0]?+=?end?-?begin;
          ????????????????}
          ????????????????countDownLatch.countDown();
          ????????????});
          ????????}
          ????????try?{
          ????????????countDownLatch.await();
          ????????}?catch?(InterruptedException?e)?{
          ????????????e.printStackTrace();
          ????????}
          ????????return?sum[0];
          ????}


          ????/**
          ?????*?緩存時鐘,緩存System.currentTimeMillis()的值,每隔20ms更新一次
          ?????*/

          ????public?static?class?CacheClock{
          ????????//定時任務(wù)調(diào)度線程池
          ????????private?static?ScheduledExecutorService?timer?=?new?ScheduledThreadPoolExecutor(1);
          ????????//毫秒緩存
          ????????private?static?volatile?long?timeMilis;??????
          ????????static?{
          ????????????//每秒更新毫秒緩存
          ????????????timer.scheduleAtFixedRate(new?Runnable()?{
          ????????????????@Override
          ????????????????public?void?run()?{
          ????????????????????timeMilis?=?System.currentTimeMillis();
          ????????????????}
          ????????????},0,1000,TimeUnit.MILLISECONDS);
          ????????}
          ????????
          ????????public?static?long?currentTimeMillis()?{
          ????????????return?timeMilis;
          ????????}
          ????}
          }

          使用JMH的測試代碼:

          @BenchmarkMode(Mode.AverageTime)
          @OutputTimeUnit(TimeUnit.MICROSECONDS)
          //120輪預(yù)熱,充分利用JIT的編譯優(yōu)化技術(shù)
          @Warmup(iterations?=?120,time?=?1,timeUnit?=?TimeUnit.MILLISECONDS)
          @Measurement(time?=?1,timeUnit?=?TimeUnit.MICROSECONDS)
          //線程數(shù):CPU*2(計算復(fù)雜型,也有CPU+1的說法)
          @Threads(8)
          @Fork(1)
          @State(Scope.Benchmark)
          public?class?JMHTest?{

          ????public?static?void?main(String[]?args)?throws?RunnerException?{
          ????????testNTime(10000);
          ????}

          ????private?static?void?testNTime(int?num)?throws?RunnerException?{
          ????????Options?options?=?new?OptionsBuilder()
          ????????????????.include(JMHTest.class.getSimpleName())
          ????????????????.measurementIterations(num)
          ????????????????.output("E://testRecord.log")
          ????????????????.build()
          ;
          ????????new?Runner(options).run();
          ????}


          ????/**
          ?????*?System.currentMillisTime測試
          ?????*?@return?將結(jié)果返回是為了防止死碼消除(編譯器將?無引用的變量?當(dāng)成無用代碼優(yōu)化掉)
          ?????*/

          ????@Benchmark
          ????public?long?testSystem()?{
          ????????return?System.currentTimeMillis();
          ????}

          ????/**
          ?????*?緩存時鐘測試
          ?????*?@return
          ?????*/

          ????@Benchmark
          ????public?long?testCacheClock()?{
          ????????return?JMHTest.CacheClock.currentTimeMillis();
          ????}

          ????/**
          ?????*?緩存時鐘,緩存System.currentTimeMillis()的值,每隔1s更新一次
          ?????*/

          ????public?static?class?CacheClock{
          ????????private?static?ScheduledExecutorService?timer?=?new?ScheduledThreadPoolExecutor(1);
          ????????private?static?volatile?long?timeMilis;
          ????????static?{
          ????????????timer.scheduleAtFixedRate(new?Runnable()?{
          ????????????????@Override
          ????????????????public?void?run()?{
          ????????????????????timeMilis?=?System.currentTimeMillis();
          ????????????????}
          ????????????},0,1000,TimeUnit.MILLISECONDS);
          ????????}
          ????????public?static?long?currentTimeMillis()?{
          ????????????return?timeMilis;
          ????????}
          ????}
          }





          后臺回復(fù)?學(xué)習(xí)資料?領(lǐng)取學(xué)習(xí)視頻


          如有收獲,點個在看,誠摯感謝


          瀏覽 56
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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>
                  www亚洲无 码A片 | 国产黄色视频观看 | 免费在线观看V片 | 亚洲色情精品视频 | 亚洲成人无码网站 |