美文网首页
System.currentTimeMillis() 在高并发环

System.currentTimeMillis() 在高并发环

作者: 寒山月下 | 来源:发表于2020-12-16 13:29 被阅读0次

据说在 java 中采用 System.currentTimeMillis() 获取时间时,如果在高并发情况下很可能会出现性能问题。今天简单做了测试,简单做下总结,以便在以后的工作中使用的时候注意到该问题。

1. 测试代码如下

public static void main(String[] args) throws InterruptedException {
    // 获取时间总次数
    int threadCount = 100;
    
    // 单线程循环获取
    long singleStartTime = System.nanoTime();
    for(int i=0; i<threadCount; i++) {
        System.currentTimeMillis();
    }
    long singleDeltaTime = System.nanoTime() - singleStartTime;
    System.out.println("单线程" + threadCount + "次耗时:" + singleDeltaTime + "ns");
    
    // 使用线程池,多线程并发获取
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), Runtime.getRuntime().availableProcessors() * 2,
            60L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(threadCount));

    CountDownLatch subDownLatch = new CountDownLatch(threadCount);
    CountDownLatch mainCountDownLatch = new CountDownLatch(1);
    for(int i=0; i<threadCount; i++) {
        threadPoolExecutor.execute(() -> {
            try {
                // 主线程先阻塞等待
                mainCountDownLatch.await();
                System.currentTimeMillis();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                subDownLatch.countDown();
            }
        });
    }
    long startTime = System.nanoTime();
    // 主线程开始继续执行
    mainCountDownLatch.countDown();
    // 子线程等待线程池中其他活跃线程执行完
    subDownLatch.await();
    long deltaTime = System.nanoTime() - startTime;
    System.out.println("并发" + threadCount + "次耗时:" + deltaTime + "ns");

}

2. 获取 100 次,单线程和多线程并发情况下的耗时对比

1)如果并发时不使用线程池,直接通过 new Thread() 方式执行结果如下, 可以看到并发情况下是单线程循环获取耗时的将近 40 倍。

image.jpeg

2)如果使用线程池,就如上面代码中所示的,执行结果如下,可以看到是单线程循环获取耗时的 8 倍

image.jpeg

结论:

1. 多线程并发调用 System.currentTimeMillis() 方法,比单线程循环调用性能差很多。

2. 使用线程池可以有效避免线程上下文切换引起的性能问题,平时推荐使用线程池。

2. 原因

由于System.currentTimeMillis() 是native 方法,没有深入挖掘,借用网上的说法,在执行该 native 方法时会涉及从 用户态到内核态的切换,去调用 Linux 内核的时钟源,时钟源只有一个,因此在大量并发时会造成严重的资源竞争,从而导致出现性能问题。

4. 优化

在高并发情况下,可以考虑通过后台线程定时去调用 System.currentTimeMillis() 方法获取时间,并将其保存在 java 变量中,在需要获取时间时,直接获取该变量的值即可。不过该方式可能会损失一定的时间精度,损失的精度取决于定时的时间间隔,间隔越大,精度损失越严重。

不过一般的项目中,并没有必要去做此优化,笔者做过验证,在 30w 并发之内,优化后的代码并没有比直接获取性能提高。在达到 3000w 并发时,也仅仅比直接过去快了 1s。不过还是有一些用。下面是优化的代码,可以酌情参考。

public class SystemClock {

    private volatile long now;
    private long period;

    private SystemClock(long period) {
        this.period = period;
        // 先初始化为当前时间,不然第一次调用会有问题
        this.now = System.currentTimeMillis();
        // 后台线程任务开启定时刷新 now 的值
        scheduleGetSystemTime();
    }
    
    // 对外方法
    public static long currentTimeMillis() {
        return Instance.INSTANCE.getCurrentMillis();
    }

    private void scheduleGetSystemTime() {
        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(runnable -> {
            Thread thread = new Thread(runnable, "system-lock-thread");
            thread.setDaemon(true);
            return thread;
        });
        scheduledExecutorService.scheduleAtFixedRate(() -> now = System.currentTimeMillis(), 0, period, TimeUnit.MILLISECONDS);
    }

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

    private long getCurrentMillis() {
        return now;
    }
}

相关文章

网友评论

      本文标题:System.currentTimeMillis() 在高并发环

      本文链接:https://www.haomeiwen.com/subject/lnvngktx.html