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

          共 7706字,需瀏覽 16分鐘

           ·

          2021-07-29 05:38


          來源:https://juejin.cn/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(), 0L1000, 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è)普通對(duì)象耗時(shí)還要高100倍左右,那個(gè)又拿出測(cè)試記錄說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)致多線程的爭(zhēng)用
          • System.currentTimeMillis()之所以慢是因?yàn)槿?/span>跟系統(tǒng)打了一次交道
          • 我有測(cè)試記錄,并發(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)核自己基本不會(huì)使用,只是維護(hù)更新。而且讀寫xtime使用的是Linux內(nèi)核中的順序鎖,而非互斥鎖,讀線程間是互不影響的。

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

          寫操作:CAS使序列號(hào)+1

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

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

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


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


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

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


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


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


          System代表 System.currentTimeMillis

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

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


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


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


          測(cè)試結(jié)果分析


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

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


          # 最后


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


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


          最后奉上我的測(cè)試代碼


          測(cè)試代碼:

          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)); }


          /** * 單線程測(cè)試 * @return */ private static long singleThreadTest(Runnable runnable,int num) { long sum = 0; for (int i = 0; i < num; i++) { long begin = System.nanoTime(); runnable.run(); long end = System.nanoTime(); sum += end - begin; } return sum; }
          /** * 并發(fā)測(cè)試 * @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ì)算密集型場(chǎng)景,可能導(dǎo)致等待時(shí)間較長(zhǎng) CountDownLatch countDownLatch = new CountDownLatch(num); for (int i = 0; i < num; i++) { threadPoolExecutor.submit(() -> { long begin = System.nanoTime(); runnable.run(); long end = System.nanoTime(); //計(jì)算復(fù)雜型場(chǎng)景更適合使用悲觀鎖 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的測(cè)試代碼:

          @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測(cè)試 * @return 將結(jié)果返回是為了防止死碼消除(編譯器將 無引用的變量 當(dāng)成無用代碼優(yōu)化掉) */ @Benchmark public long testSystem() { return System.currentTimeMillis(); }
          /** * 緩存時(shí)鐘測(cè)試 * @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; } }}


          分享一下我寫的《10萬字Springboot經(jīng)典學(xué)習(xí)筆記》中,點(diǎn)擊下面小卡片,進(jìn)入【Java禿頭哥】,回復(fù):筆記,即可免費(fèi)獲取。

          點(diǎn)贊是最大的支持 

          瀏覽 23
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

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

          手機(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>
                  中文字幕在线观看第一页2019 | 婷婷色导航| 黄色电影视频网址 | 欧美日韩国产传媒 | AV电影网站在线观看 |