原创 System.currentTimeMillis的性能真有如此不堪吗?

发布时间:2021-06-24 17:04:43 浏览 2886 来源:猿笔记 作者:围军儿

    1.System.currentTimeMillis确实**要访问系统时钟**”xtime是Linux系统给用户空间用来获取当前时间的,内核自己基本不会使用,**而且读写xtime使用的是Linux内核中的顺序锁:有一个操作序列号,写操作会使序列号+1,读操作则不会,则证明进行读操作时没有写操作干扰。否则说明读的时侯可能数据被更改了,重新做读操作,读xtime的时候数据可能被更改吗,难度读操作不是原子性的吗,而64位机器不会产生这个并发的问题,用户进程必须进入内核态才能访问系统资源,分配内存也属于系统调用,也要进内核态跟系统打交道**。


    欢迎转载,请注明原作者和出处

    ##疑惑,System.currentTimeMillis真有性能问题?

    最近我在研究一款中间件的源代码时,发现它获取当前时间不是通过System.currentTimeMillis,而是通过自定义的System.currentTimeMillis的缓存类(见下方),难道System.currentTimeMillis的性能如此不堪吗?竟然要通过自定义的缓存时钟取而代之?

    java/***弱精度的计时器,考虑性能不使用同步策略。**@authormycat*/publicclassTimeUtil{//当前毫秒数的缓存privatestaticvolatilelongCURRENT_TIME=System.currentTimeMillis();publicstaticfinallongcurrentTimeMillis(){returnCURRENT_TIME;}publicstaticfinallongcurrentTimeNanos(){returnSystem.nanoTime();}\t//更新缓存publicstaticfinalvoidupdate(){CURRENT_TIME=System.currentTimeMillis();}}//使用定时任务调度线程池,定期(每1s)调用update方法更新缓存时钟heartbeatScheduler.scheduleAtFixedRate(processorCheck(),0L,1000,TimeUnit.MILLISECONDS);

    为了跟紧时代潮流,跟上性能优化“大师”们的步伐,我赶紧上网搜了一下“currentTimeMillis性能”,结果10个搜索结果里面有9个是关于system.currentTimeMillis性能问题的:

    点开一看,这个说System.currentTimeMillis**比new一个普通对象耗时还要高100倍左右**,那个又拿出测试记录说System.currentTimeMillis**并发情况下耗时比单线程调用高250倍**

    ##思索,System.currentTimeMillis有什么性能问题

    看到这里,我迫不及待的打开IDEA,把代码中的System.currentTimeMillis全部替换掉,但是作为一个严谨的程序员,我该怎么随大流,跟着别人走呢?所以我仔细阅读了这些文章,并总结了他们的观点:

    -System.currentTimeMillis**要访问系统时钟**,这属于临界区资源,并发情况下必然导致多线程的争用

    -System.currentTimeMillis()之所以慢是因为去**跟系统打了一次交道**

    ——我* *有测试记录* *,并发时间比单线程高250倍!

    但我细细品味,发现这些观点漏洞百出:

    1.system . CurrentiMemillis * *是否想访问系统时钟* *。准确的说是看墙上的时间(xtime)。xtime是Linux系统给用户获取当前时间的空间。内核本身基本上不会使用它,只会维护和更新它。* *此外,读写xtime使用的是Linux内核中的顺序锁,而不是互斥锁,读取线程之间互不影响* *

    你可以把顺序锁想象成一个解决ABA问题的CompareAndSwap锁。对于一个关键区域资源(这里是xtime),有一个操作序号,写操作会使序号+1,读操作不会。

    写操作:CAS做序列号+1

    读取操作:先获取序列号,读取数据,再重新获取序列号。如果前后获得的序列号相同,则证明在读取操作过程中没有写干扰,因此本次读取有效,数据返回。否则意味着在读取过程中数据可能会发生变化,这个读取是无效的,再做一次读取操作。

    你可能会有一个问题:读xtime时数据可以改变吗?难读操作不是原子的吗?这是因为xtime是64位的,32位的机器需要读两次,而64位的机器不会有这个并发问题。

    2.**一旦* *处理了系统,确实,用户进程必须进入内核状态才能访问系统资源,但是**new是对象,分配内存也是系统调用,所以必须进入内核状态才能处理系统。* *,只是读取系统墙比移动内存指针和初始化内存要耗费100倍的时间吗?极好的

    3.至于所谓的测试记录,给你看看他的测试代码:

    这个测试代码的问题是* *锁定endLatch .倒计时所花费的时间也算作总时间* *,锁定是基于CAS的。在当前计算密集型场景中,大量线程被群集,几乎所有线程都将因CAS故障而暂停。* *大量线程挂起、排队、掉线的时间* *可不是小数目。其次,* *用这个方法(从执行开始到执行结束)来比较并发和单线程的调用时间消耗也是有问题的。* *单线程如何比较多线程的总执行时间?可以比较的应该是每次通话花费的时间总和(见下文)

    javalongbegin=System.nanoTime();//单次调用System.currrentTimeMillis()longend=System.nanoTime();sum+=end-begin;>**记录每次调用的总耗时**,这种方法虽然会把System.nanoTime()也算进总耗时里,但因为不论并发测试还是单线程测试都会记录System.nanoTime(),不会导致测试的不公平##数据说话,System.currentTimeMillis的性能没有问题通过改进测试代码(测试代码见文末),并添加了优化“大师”们的缓存时钟做对比,我得到了以下数据:|次数\\耗时\\场景|单线程System|单线程缓存时钟|200线程System|200线程缓存时钟||--------------|------------|--------------|-------------|---------------||1w|3.682ms|42.844ms|0.583ms|0.444ms||10w|6.780ms|35.837ms|3.379ms|3.066ms||100w|30.764ms|70.917ms|36.416ms|27.906ms||1000w|263.287ms|427.319ms|355.452ms|261.360ms|>System代表System.currentTimeMillis>>缓存时钟代表使用静态成员变量做System.currentTimeMillis缓存的时钟类>>200线程-Tomcat的默认线程数###使用JMH(Java基准测试框架)的测试结果|测试次数\\平均耗时\\场景|System|缓存时钟||----------------------|----------------------|----------------------||1w(Windows)|0.368±0.667微秒/次|0.578±1.039微秒/次||1w(Linux)|0.478±0.393微妙/次|6.083±6.064微妙/次|>JMH按照推荐使用了双倍CPU的线程数(8线程),统计的是平均时间,测试代码见文末>另外Windos和Linux配置不同,彼此间无可比性###测试结果分析可以看到System.currentTimeMillis**并发性能并不算差,在次数较少(短期并发调用)的情况下甚至比单线程要强很多**,而在**单线程调用时效率也要比缓存时钟要高一倍左右**。实际环境中几乎是达不到上述测试中的多线程长时间并发调用System.currentTimeMillis这样的情况的,因而我认为没有必要对System.currentTimeMillis做所谓的“优化”>这里没有做“new一个对象”的测试,是因为并不是代码里写了newObject(),JVM就会真的会给你在堆内存里new一个对象。这是JVM的一个编译优化——**逃逸分析**:先分析要创建的对象的作用域,如果这个对象只在一个method里有效(局部变量对象),则属于**未方法逃逸**,不去实际创建对象,而是你在method里调了对象的哪个方法,就把这个方法的代码块内联进来。只在线程内有效则属于**未线程逃逸**,会创建对象,但会自动消除我们做的无用的同步措施。##最后纸上得来终觉浅,绝知此事要躬行想要学习JMH,请跟着GitHub官方文档走,别人的博客可能跑不通就搬上去了,笔者也是刚刚踩过了这个坑最后奉上我的测试代码###测试代码:javapublicclassCurrentTimeMillisTest{publicstaticvoidmain(String[]args){intnum=10000000;System.out.print("单线程"+num+"次System.currentTimeMillis调用总耗时:");System.out.println(singleThreadTest(()->{longl=System.currentTimeMillis();},num));System.out.print("单线程"+num+"次CacheClock.currentTimeMillis调用总耗时:");System.out.println(singleThreadTest(()->{longl=CacheClock.currentTimeMillis();},num));System.out.print("并发"+num+"次System.currentTimeMillis调用总耗时:");System.out.println(concurrentTest(()->{longl=System.currentTimeMillis();},num));System.out.print("并发"+num+"次CacheClock.currentTimeMillis调用总耗时:");System.out.println(concurrentTest(()->{longl=CacheClock.currentTimeMillis();},num));}/***单线程测试*@return*/privatestaticlongsingleThreadTest(Runnablerunnable,intnum){longsum=0;for(inti=0;i(num));long[]sum=newlong[]{0};//闭锁基于CAS实现,并不适合当前的计算密集型场景,可能导致等待时间较长CountDownLatchcountDownLatch=newCountDownLatch(num);for(inti=0;i{longbegin=System.nanoTime();runnable.run();longend=System.nanoTime();//计算复杂型场景更适合使用悲观锁synchronized(CurrentTimeMillisTest.class){sum[0]+=end-begin;}countDownLatch.countDown();});}try{countDownLatch.await();}catch(InterruptedExceptione){e.printStackTrace();}returnsum[0];}/***缓存时钟,缓存System.currentTimeMillis()的值,每隔20ms更新一次*/publicstaticclassCacheClock{//定时任务调度线程池privatestaticScheduledExecutorServicetimer=newScheduledThreadPoolExecutor(1);//毫秒缓存privatestaticvolatilelongtimeMilis;static{//每秒更新毫秒缓存timer.scheduleAtFixedRate(newRunnable(){@Overridepublicvoidrun(){timeMilis=System.currentTimeMillis();}},0,1000,TimeUnit.MILLISECONDS);}publicstaticlongcurrentTimeMillis(){returntimeMilis;}}}###使用JMH的测试代码:

    java@BenchmarkMode(Mode.AverageTime)@OutputTimeUnit(TimeUnit.MICROSECONDS)//120轮预热,充分利用JIT的编译优化技术@Warmup(iterations=120,time=1,timeUnit=TimeUnit.MILLISECONDS)@Measurement(time=1,timeUnit=TimeUnit.MICROSECONDS)//线程数:CPU*2(计算复杂型,也有CPU+1的说法)@Threads(8)@Fork(1)@State(Scope.Benchmark)publicclassJMHTest{publicstaticvoidmain(String[]args)throwsRunnerException{testNTime(10000);}privatestaticvoidtestNTime(intnum)throwsRunnerException{Optionsoptions=newOptionsBuilder().include(JMHTest.class.getSimpleName()).measurementIterations(num).output("E://testRecord.log").build();newRunner(options).run();}/***System.currentMillisTime测试*@return将结果返回是为了防止死码消除(编译器将无引用的变量当成无用代码优化掉)*/@BenchmarkpubliclongtestSystem(){returnSystem.currentTimeMillis();}/***缓存时钟测试*@return*/@BenchmarkpubliclongtestCacheClock(){returnJMHTest.CacheClock.currentTimeMillis();}/***缓存时钟,缓存System.currentTimeMillis()的值,每隔1s更新一次*/publicstaticclassCacheClock{privatestaticScheduledExecutorServicetimer=newScheduledThreadPoolExecutor(1);privatestaticvolatilelongtimeMilis;static{timer.scheduleAtFixedRate(newRunnable(){@Overridepublicvoidrun(){timeMilis=System.currentTimeMillis();}},0,1000,TimeUnit.MILLISECONDS);}publicstaticlongcurrentTimeMillis(){returntimeMilis;}}}

作者信息

围军儿 [等级:3] Java后端
发布了 7 篇专栏 · 获得点赞 54 · 获得阅读 8701

相关推荐 更多