好记忆不如烂笔头,能记下点东西,就记下点,有时间拿出来看看,也会发觉不一样的感受.

System.currentTimeMillis()是极其常用的基础Java API,广泛地用来获取时间戳或测量代码执行时长等,在我们的印象中应该快如闪电。但实际上在并发调用或者特别频繁调用它的情况下(比如一个业务繁忙的接口,或者吞吐量大的需要取得时间戳的流式程序),其性能表现会令人大跌眼镜。直接看下面的Demo。

 1 public class CurrentTimeMillisPerfDemo {
2     private static final int COUNT = 100;
3
4     public static void main(String[] args) throws Exception {
5         long beginTime = System.nanoTime();
6         for (int i = 0; i < COUNT; i++) {
7             System.currentTimeMillis();
8         }
9
10         long elapsedTime = System.nanoTime() - beginTime;
11         System.out.println("100 System.currentTimeMillis() serial calls: " + elapsedTime + " ns");
12
13         CountDownLatch startLatch = new CountDownLatch(1);
14         CountDownLatch endLatch = new CountDownLatch(COUNT);
15         for (int i = 0; i < COUNT; i++) {
16             new Thread(() -> {
17                 try {
18                     startLatch.await();
19                     System.currentTimeMillis();
20                 } catch (InterruptedException e) {
21                     e.printStackTrace();
22                 } finally {
23                     endLatch.countDown();
24                 }
25             }).start();
26         }
27
28         beginTime = System.nanoTime();
29         startLatch.countDown();
30         endLatch.await();
31         elapsedTime = System.nanoTime() - beginTime;
32         System.out.println("100 System.currentTimeMillis() parallel calls: " + elapsedTime + " ns");
33     }
34 }

demo

执行结果如下图。

可见,并发调用System.currentTimeMillis()一百次,耗费的时间是单线程调用一百次的250倍。如果单线程的调用频次增加(比如达到每毫秒数次的地步),也会观察到类似的情况。实际上在极端情况下,System.currentTimeMillis()的耗时甚至会比创建一个简单的对象实例还要多,看官可以自行将上面线程中的语句换成new HashMap<>之类的试试看。

为什么会这样呢?来到HotSpot源码的hotspot/src/os/linux/vm/os_linux.cpp文件中,有一个javaTimeMillis()方法,这就是System.currentTimeMillis()的native实现。

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

native Code

挖源码就到此为止,因为已经有国外大佬深入到了汇编的级别来探究,详情可以参见《The Slow currentTimeMillis()》这篇文章,我就不班门弄斧了。简单来讲就是:

  • 调用gettimeofday()需要从用户态切换到内核态;

  • gettimeofday()的表现受Linux系统的计时器(时钟源)影响,在HPET计时器下性能尤其差;

  • 系统只有一个全局时钟源,高并发或频繁访问会造成严重的争用。

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

另外,可以用以下的命令查看和修改时钟源。

1 ~ cat /sys/devices/system/clocksource/clocksource0/available_clocksource
2 tsc hpet acpi_pm
3 ~ cat /sys/devices/system/clocksource/clocksource0/current_clocksource
4 tsc
5 ~ echo 'hpet' > /sys/devices/system/clocksource/clocksource0/current_clocksource

命令

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

 1 public class CurrentTimeMillisClock {
2     private volatile long now;
3
4     private CurrentTimeMillisClock() {
5         this.now = System.currentTimeMillis();
6         scheduleTick();
7     }
8
9     private void scheduleTick() {
10         new ScheduledThreadPoolExecutor(1, runnable -> {
11             Thread thread = new Thread(runnable, "current-time-millis");
12             thread.setDaemon(true);
13             return thread;
14         }).scheduleAtFixedRate(() -> {
15             now = System.currentTimeMillis();
16         }, 1, 1, TimeUnit.MILLISECONDS);
17     }
18
19     public long now() {
20         return now;
21     }
22     
23     public static CurrentTimeMillisClock getInstance() {
24         return SingletonHolder.INSTANCE;
25     }
26
27     private static class SingletonHolder {
28         private static final CurrentTimeMillisClock INSTANCE = new CurrentTimeMillisClock();
29     }
30 }

使用的时候,直接CurrentTimeMillisClock.getInstance().now()就可以了。不过,在System.currentTimeMillis()的效率没有影响程序整体的效率时,就不必忙着做优化,这只是为极端情况准备的。

其他不涉及到时间戳的方法:System.currentTimeMillis

 例如:UUID.randomUUID().toString().replace("-", "");
 
 
参考:https://blog.csdn.net/supingemail/article/details/106400422?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-0&spm=1001.2101.3001.4242
 

并发慎用——System.currentTimeMillis()的更多相关文章

  1. 高并发场景下System.currentTimeMillis()的性能问题的优化 以及SnowFlakeIdWorker高性能ID生成器

    package xxx; import java.sql.Timestamp; import java.util.concurrent.*; import java.util.concurrent.a ...

  2. 高并发场景下System.currentTimeMillis()的性能优化

    一.前言 System.currentTimeMillis()的调用比new一个普通对象要耗时的多(具体耗时高出多少我也不知道,不过听说在100倍左右),然而该方法又是一个常用方法, 有时不得不使用, ...

  3. 高并发场景下System.currentTimeMillis()的性能问题的优化

    高并发场景下System.currentTimeMillis()的性能问题的优化 package cn.ucaner.alpaca.common.util.key; import java.sql.T ...

  4. System.nanoTime()和System.currentTimeMillis()性能问题

    ​ 之前给模块做性能优化的时候,需要将性能调到毫秒级,使用了System.nanoTime()和System.currentTimeMillis()对代码分片计时分析耗时操作,后发现在串行情况下性能达 ...

  5. 雪花算法对System.currentTimeMillis()优化真的有用么?

    前面已经讲过了雪花算法,里面使用了System.currentTimeMillis()获取时间,有一种说法是认为System.currentTimeMillis()慢,是因为每次调用都会去跟系统打一次 ...

  6. 别再用 System.currentTimeMillis 统计耗时了,太 Low,试试 Spring Boot 源码在用的 StopWatch吧,够优雅!

    大家好,我是二哥呀! 昨天,一位球友问我能不能给他解释一下 @SpringBootApplication 注解是什么意思,还有 Spring Boot 的运行原理,于是我就带着他扒拉了一下这个注解的源 ...

  7. System.nanoTime与System.currentTimeMillis的理解与区别

    System类代表系统,系统级的很多属性和控制方法都放置在该类的内部.该类位于java.lang包. 平时产生随机数时我们经常拿时间做种子,比如用System.currentTimeMillis的结果 ...

  8. 由system.currentTimeMillis() 获得当前的时间

    System类代表系统,系统级的很多属性和控制方法都放置在该类的内部.该类位于java.lang包. currentTimeMillis方法 public static long currentTim ...

  9. c# 实现 java 的 System.currentTimeMillis() 值

    本文地址:http://www.cnblogs.com/jying/p/3875331.html 以下一句即可实现 java 中的 System.currentTimeMillis() 值 , , , ...

  10. System.currentTimeMillis()与SystemClock.uptimeMillis()

    1.System.currentTimeMillis()获取的是系统的时间,可以使用SystemClock.setCurrentTimeMillis(long millis)进行设置.如果使用Syst ...

随机推荐

  1. 不同linux发行版 FIO测试结果总结

    不同linux发行版 FIO测试结果总结 背景 机器来源 配置: 2路28核心Golden 6330 2.0Ghz 512G内存 硬盘 24块 960G SSD (22块 Raid5 + 2块 hot ...

  2. [转帖]Tomcat 优雅关闭之路

    本文首发于 vivo互联网技术 微信公众号链接:https://mp.weixin.qq.com/s/ZqkmoAR4JEYr0x0Suoq7QQ作者:马运杰 本文通过阅读Tomcat启动和关闭流程的 ...

  3. [转帖]gcc与makefile常用操作(绝对常用,也绝对够用)

    makefile与gcc常用操作 一.温故知新 1.可执行程序的生成过程 2.gcc的常用操作 二.make操作 三.编写Makefile文件时常用操作 注意:在Makefile文件中 空格和缩进是完 ...

  4. [转帖] Linux命令拾遗-文本处理篇

    https://www.cnblogs.com/codelogs/p/16060413.html 简介# 这是Linux命令拾遗系列的第二篇,本篇主要介绍Linux中与文本处理相关的命令,如xargs ...

  5. Nginx反向代理总结

    反向代理的种类 1. LVS的方案 2. DNS轮询的方案 3. Nginx的4层代理 4. Nginx的7层代理 5. 网络NAT的处理 Nginx的反向代理-四层` 编译时增加 --with-st ...

  6. Redis Cluster in K3S

    Redis Cluster in K3S 学习资料 https://www.cnblogs.com/cheyunhua/p/15619317.html https://blog.csdn.net/cq ...

  7. CS231N Assignment1 SVM 笔记

    svm.ipynb 为SVM实现一个完全矢量化的损失函数 为其解析梯度实现完全矢量化表达式 使用数值梯度检查实现结果 使用验证集调整学习率和正则化 使用 SGD 优化损失函数 可视化最终学习权重 第一 ...

  8. 【记录一个问题】gin框架中,ShouldBindUri()函数依赖特定版本编译器,更换库的版本号后导致panic

    panic发生在这一行: uriBindErr = c.ShouldBindUri(methodLastInParam.Interface()) 导致panic的堆栈信息如下: err=reflect ...

  9. 【K哥爬虫普法】辛苦钱被中间商抽走八成,还因此锒铛入狱

    我国目前并未出台专门针对网络爬虫技术的法律规范,但在司法实践中,相关判决已屡见不鲜,K 哥特设了"K哥爬虫普法"专栏,本栏目通过对真实案例的分析,旨在提高广大爬虫工程师的法律意识, ...

  10. jenkins 安装与构建过程中的系列问题

    一.插件安装遇到的依赖问题 插件安装分为在线安装和离线安装 1.在线安装 搜索要安装的插件,然后进行安装即可 2.离线安装hpi文件 使用该方法安装插件每次只能安装一个插件,且如果插件之间存在依赖性则 ...