zoukankan      html  css  js  c++  java
  • System.currentTimeMillis()的性能问题

    一直觉得java原生API都是性能很高的,今天看一篇博客时,说到System.currentTimeMillis()的性能十分低下,觉得很奇怪,于是写了一些代码来尝试了一下

    public class CurrentTimeTest {
        private static final int COUNT = 100;
        public static void main(String[] args) throws Exception {
            long beginTime = System.nanoTime();
            for (int i = 0; i < COUNT; i++) {
                System.currentTimeMillis();
            }
            long elapsedTime = System.nanoTime() - beginTime;
            System.out.println("单线程100次   System.currentTimeMillis : " + elapsedTime + " ns");
            CountDownLatch startLatch = new CountDownLatch(1);
            CountDownLatch endLatch = new CountDownLatch(COUNT);
            for (int i = 0; i < COUNT; i++) {
                new Thread(() -> {
                   try {
                       startLatch.await();
                       System.currentTimeMillis();
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   } finally {
                       endLatch.countDown();
                   }
                }).start();
            }
            beginTime = System.nanoTime();
            startLatch.countDown();
            endLatch.await();
            elapsedTime = System.nanoTime() - beginTime;
            System.out.println("100线程并发下 System.currentTimeMillis : " + elapsedTime + " ns");
        }
    }

    执行结果如下:

    可见System.currentTimeMoillis一百次耗费的时间非常大,尤其是并发状态下比单线程高出一个量级,甚至极端情况比创建对象更耗费资源

    查看HotSpot源码的hotspot/src/os/linux/vm/os_linux.cpp,有一个javaTimeMillis方法,这就是System.currentTimeMillis的native实现。

    jlong os::javaTimeMillis {
        timeval time;
        int status = gettimeofday(&time, NULL);
        assert(status != -1, "linux error");
        return jlong(time.tv_sec) * 1000 + jlong(time.tv_usec / 1000);
    }

    对于这部分源码已经有国外大佬深入到了汇编的级别来探究,详情可以参见《The Slow currentTimeMillis》

    简单来讲就是:
        1.调用gettimeofday需要从用户态切换到内核态;
        2.gettimeofday的表现受Linux系统的计时器(时钟源)影响,在HPET计时器下性能尤其差;
        3.系统只有一个全局时钟源,高并发或频繁访问会造成严重的争用。

    HPET计时器性能较差的原因是会将所有对时间戳的请求串行执行。TSC计时器性能较好,因为有专用的寄存器来保存时间戳。缺点是可能不稳定,因为它是纯硬件的计时器,频率可变(与处理器的CLK信号有关)。关于HPET和TSC的细节可以参见下面的链接,就不做过多讨论
    https://en.wikipedia.org/wiki/HighPrecisionEventTimer
    https://en.wikipedia.org/wiki/TimeStamp_Counter

    那么如何避免这个问题?最常见的办法是用单个调度线程来按毫秒更新时间戳,相当于维护一个全局缓存。其他线程取时间戳时相当于从内存取,不会再造成时钟资源的争用,代价就是牺牲了一些精确度。具体代码如下。

    @Component
    public class TimeServcie {
        private static long time;
    
        static {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {
                        try {
                            Thread.sleep(5);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        long cur = System.currentTimeMillis();
                        setTime(cur);
                    }
                }
            }).start();
        }
    
        public static long getTime() {
            return time;
        }
    
        public static void setTime(long time) {
            TimeServcie.time = time;
        }
    }
  • 相关阅读:
    Django入门
    RCNN 研究相关
    [Android UI]View滑动方式总结
    [Android UI]View基础知识
    [Android]Android开发艺术探索第1章笔记
    [Leetcode]017. Letter Combinations of a Phone Number
    java之this关键字
    POJ 1000 A+B
    [Leetcode]016. 3Sum Closest
    [Leetcode]015. 3Sum
  • 原文地址:https://www.cnblogs.com/gtblog/p/12698977.html
Copyright © 2011-2022 走看看