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

          共 4396字,需瀏覽 9分鐘

           ·

          2020-11-18 03:54

          點(diǎn)擊上方藍(lán)色“程序猿DD”,選擇“設(shè)為星標(biāo)”

          回復(fù)“資源”獲取獨(dú)家整理的學(xué)習(xí)資料!

          作者:圍軍兒

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

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

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

          /**
          ?*?弱精度的計(jì)時(shí)器,考慮性能不使用同步策略。
          ?*?
          ?*?@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();?}

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

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

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

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

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

          • System.currentTimeMillis要訪問系統(tǒng)時(shí)鐘,這屬于臨界區(qū)資源,并發(fā)情況下必然導(dǎo)致多線程的爭用
          • System.currentTimeMillis()之所以慢是因?yàn)槿?strong style="color: rgb(255, 53, 2);line-height: 1.5;">跟系統(tǒng)打了一次交道
          • 有測試記錄,并發(fā)耗時(shí)就是比單線程高250倍!

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

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

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

            寫操作:CAS使序列號+1

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

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

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

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

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

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

            記錄每次調(diào)用的總耗時(shí),這種方法雖然會把System.nanoTime()也算進(jìn)總耗時(shí)里,但因?yàn)椴徽摬l(fā)測試還是單線程測試都會記錄System.nanoTime(),不會導(dǎo)致測試的不公平

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

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

          次數(shù)\耗時(shí)\場景單線程System單線程緩存時(shí)鐘200線程System200線程緩存時(shí)鐘
          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

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

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

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

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

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

          測試結(jié)果分析

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

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

          最后

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

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

          最后奉上我的測試代碼

          測試代碼:

          public?class?CurrentTimeMillisTest?{

          ????public?static?void?main(String[]?args)?{
          ????????int?num?=?10000000;
          ????????System.out.print("單線程"+num+"次System.currentTimeMillis調(diào)用總耗時(shí):????");
          ????????System.out.println(singleThreadTest(()?->?{
          ????????????long?l?=?System.currentTimeMillis();
          ????????},num));
          ????????System.out.print("單線程"+num+"次CacheClock.currentTimeMillis調(diào)用總耗時(shí):");
          ????????System.out.println(singleThreadTest(()?->?{
          ????????????long?l?=?CacheClock.currentTimeMillis();
          ????????},num));
          ????????System.out.print("并發(fā)"+num+"次System.currentTimeMillis調(diào)用總耗時(shí):??????");
          ????????System.out.println(concurrentTest(()?->?{
          ????????????long?l?=?System.currentTimeMillis();
          ????????},num));
          ????????System.out.print("并發(fā)"+num+"次CacheClock.currentTimeMillis調(diào)用總耗時(shí):??");
          ????????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實(shí)現(xiàn),并不適合當(dāng)前的計(jì)算密集型場景,可能導(dǎo)致等待時(shí)間較長
          ????????CountDownLatch?countDownLatch?=?new?CountDownLatch(num);
          ????????for?(int?i?=?0;?i?????????????threadPoolExecutor.submit(()?->?{
          ????????????????long?begin?=?System.nanoTime();
          ????????????????runnable.run();
          ????????????????long?end?=?System.nanoTime();
          ????????????????//計(jì)算復(fù)雜型場景更適合使用悲觀鎖
          ????????????????synchronized(CurrentTimeMillisTest.class)?{
          ????????????????????sum[0]?+=?end?-?begin;
          ????????????????}
          ????????????????countDownLatch.countDown();
          ????????????});
          ????????}
          ????????try?{
          ????????????countDownLatch.await();
          ????????}?catch?(InterruptedException?e)?{
          ????????????e.printStackTrace();
          ????????}
          ????????return?sum[0];
          ????}


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

          ????public?static?class?CacheClock{
          ????????//定時(shí)任務(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(計(jì)算復(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();
          ????}

          ????/**
          ?????*?緩存時(shí)鐘測試
          ?????*?@return
          ?????*/

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

          ????/**
          ?????*?緩存時(shí)鐘,緩存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;
          ????????}
          ????}
          }


          DD自研的滬牌代拍業(yè)務(wù),點(diǎn)擊直達(dá)


          往期推薦

          因退休太無聊,Python創(chuàng)始人加入微軟!

          HikariCP為什么自己造了一個(gè)FastList?

          Spring Boot 2.4.0 正式發(fā)布!擁抱云原生!

          服務(wù)網(wǎng)格仍然很難

          10道棘手的Java面試題,看看你能答對幾個(gè)?

          如果MySQL磁盤滿了,會發(fā)生什么?


          掃一掃,關(guān)注我

          一起學(xué)習(xí),一起進(jìn)步

          每周贈(zèng)書,福利不斷

          深度內(nèi)容

          推薦加入



          瀏覽 29
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  欧美熟女BB | 欧美性爱亚洲性爱 | 你懂的在线视频观看 | 国产精品一区二区在线播放 | 蜜桃视频在线观看一区 |