点击关注公众号,Java干货及时送达

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

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

/*** 弱精度的计时器,考虑性能不使用同步策略。* * @author mycat*/
public class TimeUtil {//当前毫秒数的缓存private static volatile long CURRENT_TIME = System.currentTimeMillis();public static final long currentTimeMillis() { return CURRENT_TIME; }public static final long currentTimeNanos() { return System.nanoTime(); }//更新缓存public static final void update() { 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.currentTimeMillis 确实要访问系统时钟,准确的说,是读取墙上时间(xtime),xtime是Linux系统给用户空间用来获取当前时间的,内核自己基本不会使用,只是维护更新。而且读写xtime使用的是Linux内核中的顺序锁,而非互斥锁,读线程间是互不影响的

大家可以把顺序锁当成是解决了“ABA问题”的CompareAndSwap锁。对于一个临界区资源(这里是xtime),有一个操作序列号,写操作会使序列号+1,读操作则不会。

  • 写操作:CAS使序列号+1

  • 读操作:先获取序列号,读取数据,再获取一次序列号,前后两次获取的序列号相同,则证明进行读操作时没有写操作干扰,那么这次读是有效的,返回数据,否则说明读的时侯可能数据被更改了,这次读无效,重新做读操作。

大家可能有个疑问:读xtime的时候数据可能被更改吗?难度读操作不是原子性的吗?这是因为xtime是64位的,对于32位机器是需要分两次读的,而64位机器不会产生这个并发的问题。

2.跟系统打了一次交道,确实,用户进程必须进入内核态才能访问系统资源,但是,new一个对象,分配内存也属于系统调用,也要进内核态跟系统打交道,难道只是读一下系统的墙上时间,会比移动内存指针,初始化内存的耗时还要高100倍吗?匪夷所思

不敢相信?System.currentTimeMillis()存在性能问题

3.至于所谓的测试记录,给大家看一下他的测试代码:

这个测试代码的问题在于闭锁endLatch.countDown的耗时也被算进总体耗时了,闭锁是基于CAS实现的,在当前这样的计算密集型场景下,大量线程一拥而上,几乎都会因CAS失败而被挂起,大量线程挂起、排队、放下的耗时可不是小数目。

其次使用这种方法(执行开始到执行完毕)来对比并发和单线程的调用耗时也有问题,单线程怎么和多线程比总的执行时间?能比的应该是每次调用的耗时之和才对(见下)

long begin = System.nanoTime();
//单次调用System.currrentTimeMillis()
long end = System.nanoTime();
sum += end - begin;

记录每次调用的总耗时,这种方法虽然会把System.nanoTime()也算进总耗时里,但因为不论并发测试还是单线程测试都会记录System.nanoTime(),不会导致测试的不公平

数据说话,System.currentTimeMillis的性能没有问题

通过改进测试代码(测试代码见文末),并添加了优化“大师”们的缓存时钟做对比,我得到了以下数据:

System代表 System.currentTimeMillis

缓存时钟代表 使用静态成员变量做System.currentTimeMillis缓存的时钟类

200线程-Tomcat的默认线程数

使用JMH(Java基准测试框架)的测试结果

JMH按照推荐使用了双倍CPU的线程数(8线程),统计的是平均时间,测试代码见文末 另外Windos和Linux配置不同,彼此间无可比性

测试结果分析

可以看到System.currentTimeMillis并发性能并不算差,在次数较少(短期并发调用)的情况下甚至比单线程要强很多,而在单线程调用时效率也要比缓存时钟要高一倍左右。实际环境中几乎是达不到上述测试中的多线程长时间并发调用System.currentTimeMillis这样的情况的,因而我认为没有必要对System.currentTimeMillis做所谓的“优化”

这里没有做“new一个对象”的测试,是因为并不是代码里写了new Object(),JVM就会真的会给你在堆内存里new一个对象。

这是JVM的一个编译优化——逃逸分析:先分析要创建的对象的作用域,如果这个对象只在一个method里有效(局部变量对象),则属于未 方法逃逸,不去实际创建对象,而是你在method里调了对象的哪个方法,就把这个方法的代码块内联进来。只在线程内有效则属于未 线程逃逸,会创建对象,但会自动消除我们做的无用的同步措施。

最后

纸上得来终觉浅,绝知此事要躬行

想要学习JMH,请跟着GitHub官方文档走,别人的博客可能跑不通就搬上去了,笔者也是刚刚踩过了这个坑

最后奉上我的测试代码

测试代码:

public class CurrentTimeMillisTest {public static void main(String[] args) {int num = 10000000;System.out.print("单线程"+num+"次System.currentTimeMillis调用总耗时:    ");System.out.println(singleThreadTest(() -> {long l = System.currentTimeMillis();},num));System.out.print("单线程"+num+"次CacheClock.currentTimeMillis调用总耗时:");System.out.println(singleThreadTest(() -> {long l = CacheClock.currentTimeMillis();},num));System.out.print("并发"+num+"次System.currentTimeMillis调用总耗时:      ");System.out.println(concurrentTest(() -> {long l = System.currentTimeMillis();},num));System.out.print("并发"+num+"次CacheClock.currentTimeMillis调用总耗时:  ");System.out.println(concurrentTest(() -> {long l = CacheClock.currentTimeMillis();},num));}/*** 单线程测试* @return*/private static long singleThreadTest(Runnable runnable,int num) {long sum = 0;for (int i = 0; i < num; i++) {long begin = System.nanoTime();runnable.run();long end = System.nanoTime();sum += end - begin;}return sum;}/*** 并发测试* @return*/private static long concurrentTest(Runnable runnable,int num) {ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(200,200,60, TimeUnit.SECONDS,new LinkedBlockingQueue<>(num));long[] sum = new long[]{0};//闭锁基于CAS实现,并不适合当前的计算密集型场景,可能导致等待时间较长CountDownLatch countDownLatch = new CountDownLatch(num);for (int i = 0; i < num; i++) {threadPoolExecutor.submit(() -> {long begin = System.nanoTime();runnable.run();long end = System.nanoTime();//计算复杂型场景更适合使用悲观锁synchronized(CurrentTimeMillisTest.class) {sum[0] += end - begin;}countDownLatch.countDown();});}try {countDownLatch.await();} catch (InterruptedException e) {e.printStackTrace();}return sum[0];}/*** 缓存时钟,缓存System.currentTimeMillis()的值,每隔20ms更新一次*/public static class CacheClock{//定时任务调度线程池private static ScheduledExecutorService timer = new ScheduledThreadPoolExecutor(1);//毫秒缓存private static volatile long timeMilis;      static {//每秒更新毫秒缓存timer.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {timeMilis = System.currentTimeMillis();}},0,1000,TimeUnit.MILLISECONDS);}public static long currentTimeMillis() {return timeMilis;}}
}

使用JMH的测试代码:

@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)
public class JMHTest {public static void main(String[] args) throws RunnerException {testNTime(10000);}private static void testNTime(int num) throws RunnerException {Options options = new OptionsBuilder().include(JMHTest.class.getSimpleName()).measurementIterations(num).output("E://testRecord.log").build();new Runner(options).run();}/*** System.currentMillisTime测试* @return 将结果返回是为了防止死码消除(编译器将 无引用的变量 当成无用代码优化掉)*/@Benchmarkpublic long testSystem() {return System.currentTimeMillis();}/*** 缓存时钟测试* @return*/@Benchmarkpublic long testCacheClock() {return JMHTest.CacheClock.currentTimeMillis();}/*** 缓存时钟,缓存System.currentTimeMillis()的值,每隔1s更新一次*/public static class CacheClock{private static ScheduledExecutorService timer = new ScheduledThreadPoolExecutor(1);private static volatile long timeMilis;static {timer.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {timeMilis = System.currentTimeMillis();}},0,1000,TimeUnit.MILLISECONDS);}public static long currentTimeMillis() {return timeMilis;}}
}

(感谢阅读,希望对你所有帮助)

来源:https://juejin.cn/post/6887743425437925383

BAT等大厂Java面试经验总结 想获取 Java大厂面试题学习资料扫下方二维码回复「BAT」就好了回复 【加群】获取github掘金交流群回复 【电子书】获取2020电子书教程回复 【C】获取全套C语言学习知识手册回复 【Java】获取java相关的视频教程和资料回复 【爬虫】获取SpringCloud相关多的学习资料回复 【Python】即可获得Python基础到进阶的学习教程回复 【idea破解】即可获得intellij idea相关的破解教程关注我gitHub掘金,每天发掘一篇好项目,学习技术不迷路!回复 【idea激活】即可获得idea的激活方式
回复 【Java】获取java相关的视频教程和资料
回复 【SpringCloud】获取SpringCloud相关多的学习资料
回复 【python】获取全套0基础Python知识手册
回复 【2020】获取2020java相关面试题教程
回复 【加群】即可加入终端研发部相关的技术交流群为什么HTTPS是安全的
因为BitMap,白白搭进去8台服务器...
《某厂内部SQL大全 》.PDF
字节跳动一面:i++ 是线程安全的吗?
大家好,欢迎加我微信,很高兴认识你!
在华为鸿蒙 OS 上尝鲜,我的第一个“hello world”,起飞!相信自己,没有做不到的,只有想不到的在这里获得的不仅仅是技术!个“在看”

疑惑,System.currentTimeMillis真有性能问题?相关推荐

  1. System.currentTimeMillis()竟然存在性能问题,这我能信?

    点击上方"方志朋",选择"设为星标" 做积极的人,而不是积极废人 来源:https://dwz.cn/M1NXgypa 在之前的文章中就提到了,System.c ...

  2. 注意System.currentTimeMillis()潜在的性能问题

    System.currentTimeMillis()是极其常用的基础Java API,广泛地用来获取时间戳或测量代码执行时长等,在我们的印象中应该快如闪电.但实际上在并发调用或者特别频繁调用它的情况下 ...

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

    推荐大家关注一个公众号 点击上方 "编程技术圈"关注, 星标或置顶一起成长 后台回复"大礼包"有惊喜礼包! 每日英文 Two things always to ...

  4. System.currentTimeMillis的性能,真有如此不堪吗?

    以下文章来源方志朋的博客,回复"666"获面试宝典 # 疑惑,System.currentTimeMillis真有性能问题? 最近我在研究一款中间件的源代码时,发现它获取当前时间不 ...

  5. Bullsh*t,System. currentTimeMillis大胆用起来,我说的!

    以下文章来源方志朋的博客,回复"666"获面试宝典 # 疑惑,System.currentTimeMillis真有性能问题? 最近我在研究一款中间件的源代码时,发现它获取当前时间不 ...

  6. 不敢相信?System.currentTimeMillis()存在性能问题

    点击蓝色"程序猿DD"关注我 回复"资源"获取独家整理的学习资料! 推荐一个免费的写博神器:openwrite.cn.Markdown一次编写,轻松发布到CSD ...

  7. System.currentTimeMillis()存在性能问题

    System.currentTimeMillis()是极其常用的基础Java API,广泛地用来获取时间戳或测量代码执行时长等,在我们的印象中应该快如闪电.但实际上在并发调用或者特别频繁调用它的情况下 ...

  8. Java的System.currentTimeMillis()的性能问题

    System.currentTimeMillis()是极其常用的基础Java API,广泛地用来获取时间戳或测量代码执行时长等,在我们的印象中应该快如闪电.但实际上在并发调用或者特别频繁调用它的情况下 ...

  9. java currenttimemillis 效率_高并发场景下System.currentTimeMillis()的性能问题的优化

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

最新文章

  1. 有道精品课python-网易词典在线翻译
  2. public-private-protected-默认缺省 的区别
  3. detectandcompute 图像尺寸太大_基于深度局部特征的图像检索
  4. Hive_Hive的数据模型_内部表
  5. html不支持元素video,Html5中的Video元素使用方法
  6. 并发编程实践之公平有界阻塞队列实现
  7. 天梯赛L2-010 排座位(并查集和\set)
  8. nlv sqlserver_SQLServer数据类型及使用分析
  9. Dev c++下载、安装、使用教程(文件、项目)
  10. Win11如何给系统盘瘦身?Win11系统盘瘦身方法
  11. IDEA主题SublimeTest3修改
  12. 设计模式经典书籍推荐
  13. 简述商业模式、商业模式画布与商业模式个人画布
  14. Python实现简单自动升级exe程序版本并自动运行
  15. 计算机显卡驱动全部卸载,如何卸载显卡驱动重新安装?Win10卸载显卡驱动+重装显卡驱动的方法...
  16. 若重新启动ratel,需确定是否还存在僵死进程
  17. 微信小程序-云开发云调用API没有权限(no permission)问题
  18. 论文阅读三:基于改进人工蜂群算法的SDN负载均衡策略研究
  19. 概念图创作-IHMC CmapTools
  20. ubuntu 16.04安装体验网易云音乐

热门文章

  1. 安化云台山风景区,2022东坪完小研学旅行实践活动
  2. 轻量级web服务器-Nginx的入门
  3. VS Code C++配置opencv:执行时报错无法定位程序输入点于动态链接库XXX
  4. 进程间通信、死锁、信号量、PV原语
  5. 神经网络模型的基本原理,常用的神经网络模型
  6. 程矢Axure夜话:Axure手机原型视频教程之微信公众ETC菜单与输入
  7. 【iBoard电子学堂开发板例程】【12个 stm32 例程发布】
  8. 查找算法5——哈希表查找
  9. Linux Ubuntu安装教程
  10. 荣耀x10和荣耀30s哪个好?