System.currentTimeMillis()高并发性能优化

发布时间 2023-08-13 12:21:25作者: 楼兰胡杨

摘要:System.currentTimeMillis()性能问题的研究、测试与优化。

  性能优化使用的测试环境:

jdk版本jdk8

  操作系统:

  • macOS
  • 版本:13.2.1
  • 芯片: Apple M1
  • CPU核数:8核

  System.currentTimeMillis()是Java极其常用的 API,广泛地用来获取时间戳或统计代码执行耗时等,在我们的印象中应该快如闪电。但实际上在高并发的情况下,其性能表现令人大跌眼镜,调用开销明显变高。

    public static void main(String[] args) throws Exception {
        singleThreadTest();
        multiThreadTest();
    }
    public static void singleThreadTest() {
        //测试一百次循环,每次循环调用System.currentTimeMillis()1千万次数
        for (int t = 0; t < 100; t++) {
            StopWatch stopWatch = new StopWatch();
            stopWatch.start();
            //获取一千万次时间
            for (int i = 0; i < 10000000; i++) {
                System.currentTimeMillis();
            }
            stopWatch.stop();
            System.out.println(stopWatch.getTotalTimeMillis());
        }
    }

    public static void multiThreadTest() throws Exception {
        //100个线程各执行一次
        CountDownLatch wait = new CountDownLatch(1);
        int loopNum = 100;
        CountDownLatch threadLatch = new CountDownLatch(loopNum);
        for (int i = 0; i < loopNum; i++) {
            new Thread(() -> {
                try {
                    StopWatch watch = new StopWatch();
                    //先阻塞住所有线程
                    wait.await();
                    watch.start();
                    for (int j = 0; j < 10000; j++) {
                        System.currentTimeMillis();
                    }
                    watch.stop();
                    System.out.println(watch.getTotalTimeNanos());
                } catch (InterruptedException e) {

                } finally {
                    threadLatch.countDown();
                }
            }).start();
        }
        wait.countDown();
        threadLatch.await();
    }

  执行后,控制台打印的部分执行结果如下:

  由此可见,单线程执行System.currentTimeMillis()比多线程并发执行快了太多倍。至于为什么这么慢,感兴趣的童鞋可以去问问度娘,这里给出一个基于定时任务按照毫秒更新缓存时间戳的方案,也就是在内存中维护一个全局缓存,其它线程取时间戳时从内存读取,代价就是牺牲了一些精确度。具体代码如下:

import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

/**
 * @Author Wiener
 * @Date 2023-08-12
 * @Description: 缓存系统时间
 */
public class SystemClock {

    private final int period;
    private final AtomicLong now;

    private static final String THREAD_NAME ="wienerClock";

    private static class InstanceHolder {
        private static final SystemClock INSTANCE = new SystemClock(1);
    }

    private SystemClock(int period) {
        this.period = period;
        this.now = new AtomicLong(System.currentTimeMillis());
        scheduleClockUpdating();
    }

    private static SystemClock instance() {
        return InstanceHolder.INSTANCE;
    }

    /**
     * 供消费者调用,以替换原来的System.currentTimeMillis()
     */
    public static long now() {
        return instance().now.get();
    }
    private void scheduleClockUpdating() {
        ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(1, r -> {
            Thread thread = new Thread(r, THREAD_NAME);
            thread.setDaemon(true);
            return thread;
        });
        // 每秒获取一次系统时间,并赋值给 AtomicLong now
        scheduler.scheduleAtFixedRate(() -> now.set(System.currentTimeMillis()), period, period, TimeUnit.MILLISECONDS);
    }

}

  在并发量大的情况下,使用SystemClock.now()输出当前时间,有一定精度损失,但是提高了系统时间获取效率。

  温馨提示,在System.currentTimeMillis()的效率没有影响程序整体吞吐量时,没有必要做这种优化,这只是为高并发情况准备的。