System.currentTimeMillis的性能,真有如此不堪嗎?
閱讀本文大概需要 6.5 分鐘。
來自:https://juejin.cn/post/6887743425437925383
# 疑惑,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);

# 思索,System.currentTimeMillis有什么性能問題
System.currentTimeMillis要訪問系統(tǒng)時鐘,這屬于臨界區(qū)資源,并發(fā)情況下必然導(dǎo)致多線程的爭用 System.currentTimeMillis()之所以慢是因為去跟系統(tǒng)打了一次交道 我有測試記錄,并發(fā)耗時就是比單線程高250倍!
大家可以把順序鎖當(dāng)成是解決了“ABA問題”的CompareAndSwap鎖。對于一個臨界區(qū)資源(這里是xtime),有一個操作序列號,寫操作會使序列號+1,讀操作則不會。 寫操作:CAS使序列號+1 讀操作:先獲取序列號,讀取數(shù)據(jù),再獲取一次序列號,前后兩次獲取的序列號相同,則證明進(jìn)行讀操作時沒有寫操作干擾,那么這次讀是有效的,返回數(shù)據(jù),否則說明讀的時侯可能數(shù)據(jù)被更改了,這次讀無效,重新做讀操作。 大家可能有個疑問:讀xtime的時候數(shù)據(jù)可能被更改嗎?難度讀操作不是原子性的嗎?這是因為xtime是64位的,對于32位機(jī)器是需要分兩次讀的,而64位機(jī)器不會產(chǎn)生這個并發(fā)的問題。

long begin = System.nanoTime();//單次調(diào)用System.currrentTimeMillis()long end = System.nanoTime();sum += end - begin;
記錄每次調(diào)用的總耗時,這種方法雖然會把System.nanoTime()也算進(jìn)總耗時里,但因為不論并發(fā)測試還是單線程測試都會記錄System.nanoTime(),不會導(dǎo)致測試的不公平
# 數(shù)據(jù)說話,System.currentTimeMillis的性能沒有問題

System代表 System.currentTimeMillis 緩存時鐘代表 使用靜態(tài)成員變量做System.currentTimeMillis緩存的時鐘類 200線程-Tomcat的默認(rèn)線程數(shù)
使用JMH(Java基準(zhǔn)測試框架)的測試結(jié)果


JMH按照推薦使用了雙倍CPU的線程數(shù)(8線程),統(tǒng)計的是平均時間,測試代碼見文末
測試結(jié)果分析
這里沒有做“new一個對象”的測試,是因為并不是代碼里寫了new Object(),JVM就會真的會給你在堆內(nèi)存里new一個對象。這是JVM的一個編譯優(yōu)化——逃逸分析:先分析要創(chuàng)建的對象的作用域,如果這個對象只在一個method里有效(局部變量對象),則屬于未 方法逃逸,不去實際創(chuàng)建對象,而是你在method里調(diào)了對象的哪個方法,就把這個方法的代碼塊內(nèi)聯(lián)進(jìn)來。只在線程內(nèi)有效則屬于未 線程逃逸,會創(chuàng)建對象,但會自動消除我們做的無用的同步措施。

# 最后
測試代碼:
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 < num; 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 < num; 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() {@Overridepublic void run() {timeMilis = System.currentTimeMillis();}},0,1000,TimeUnit.MILLISECONDS);}public static long currentTimeMillis() {return timeMilis;}}}
使用JMH的測試代碼:
(Mode.AverageTime)(TimeUnit.MICROSECONDS)//120輪預(yù)熱,充分利用JIT的編譯優(yōu)化技術(shù)(iterations = 120,time = 1,timeUnit = TimeUnit.MILLISECONDS)(time = 1,timeUnit = TimeUnit.MICROSECONDS)//線程數(shù):CPU*2(計算復(fù)雜型,也有CPU+1的說法)(8)(1)(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)化掉)*/public long testSystem() {return System.currentTimeMillis();}/*** 緩存時鐘測試* @return*/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() {public void run() {timeMilis = System.currentTimeMillis();}},0,1000,TimeUnit.MILLISECONDS);}public static long currentTimeMillis() {return timeMilis;}}}
推薦閱讀:
太瘋狂!15分鐘彈出9次廣告,360瀏覽器等國產(chǎn)軟件被點(diǎn)名
if(a==1且a==2且a==3),有沒有可能為 true?
最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。
朕已閱 
評論
圖片
表情

