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

article/2025/10/30 10:53:51

# 疑惑,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倍吗?匪夷所思

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线程),统计的是平均时间,测试代码见文末


测试结果分析

可以看到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


http://chatgpt.dhexx.cn/article/OsikNfmG.shtml

相关文章

疑惑,System.currentTimeMillis真有性能问题?

点击关注公众号&#xff0c;Java干货及时送达 System.currentTimeMillis的性能真有如此不堪吗&#xff1f; 最近我在研究一款中间件的源代码时&#xff0c;发现它获取当前时间不是通过System.currentTimeMillis&#xff0c;而是通过自定义的System.currentTimeMillis的缓存类&a…

高并发下System.currentTimeMillis()性能问题及优化方案

文章目录 背景System.currentTimeMillis()性能测试单线程测试多线程测试 原因优化优化代码单线程测试多线程测试 参考 背景 最近在看asyncTool源码发现了System.currentTimeMillis存在卡顿问题&#xff0c;所以就详细研究了下。具体如何呢&#xff1f;我们来看看 System.curr…

currentTimeMillis()方法

currentTimeMillis()方法返回一个long类型的值&#xff0c;该值表示的是当前时间与1970年1月1日0时0分0秒之间的时间差&#xff0c;单位是毫秒&#xff0c;习惯上被称为时间戳 源码&#xff1a; 时间戳可以用来计算循环操作时所需要的时间&#xff1a; /*** 向goods表中插入…

Java获取当前时区时间LocalDateTime与System.currentTimeMillis

Java获取当前时区时间 System.currentTimeMillisLocalDateTime最终结果 全球根据纬度不同&#xff0c;划分不同的时区。对于此时此刻&#xff0c;大家同处同一个时间点&#xff0c;但是&#xff0c;每个时区的时间表示是不同的。Java可以使用 System.currentTimeMillis和 Loc…

关于Java currentTimeMillis方法简述

刚刚接触JAVA时&#xff0c;为了便于记录某个方法块的执行时间&#xff0c;通常都会在代码块的执行前和执行后各标记一个时间&#xff0c;取两个时间差。 但是初学者一般只会选择用LocalDateTime来标记&#xff0c;然后用Duration.between来做差值。当然&#xff0c;Duration可…

System.currentTimeMillis()计算方式与时间的单位转换

一、时间的单位转换 1秒1000毫秒(ms) 1毫秒1&#xff0f;1,000秒(s) 1秒1,000,000 微秒(μs) 1微秒1&#xff0f;1,000,000秒(s) 1秒1,000,000,000 纳秒(ns) 1纳秒1&#xff0f;1,000,000,000秒(s) 1秒1,000,000,000,000 皮秒(ps) 1皮秒1&#xff0f;1,000,000,000,000秒(s) …

Java currentTimeMillis()方法介绍

一、官方文档 参考自Java SE 8官方文档&#xff1a; 方法功能&#xff1a;返回从1970年1月1日午夜&#xff08;UTC&#xff09;开始到当前时间的毫秒值. 其中&#xff0c;需要特别说明的地方 1.午夜(midnight)指的时间是 0时0分0秒&#xff0c;UTC表示该时间是0时区的时间…

Android中Intent和IntentFilter进行通信

文章目录 Intent启动不同组件的方法ActivityServiceBroadcastReceiver Data&#xff0c;Type属性与intent-filter配置指定Action调用系统的Activity启动Activity的标准Action常量以及对应的字符串标准的Category常量以及对应的字符串 实例一查看并获取联系人电话MainActivity代…

三、Intent 和 Intent过滤器(IntentFilter)

Intent&#xff08;意图&#xff09;&#xff1a; Intent 是一个消息传递对象&#xff0c;是我们要执行操作的一个抽象描述。我们可以使用它在相应的组件中传递消息和请求。下面是它的主要使用场景&#xff1a; 1. 启动Activity 通过 startActivity() 或者 startActivityForRes…

【Android】Intent 和 Intent Filter

一. Intent 简介 Intent 是一个消息传递对象&#xff0c;您可以用来从其他应用组件请求操作。可以用于&#xff1a;启动 Activity、启动服务、传递广播。 https://developer.android.com/guide/components/intents-filtershttps://developer.android.com/guide/components/i…

Android开发——IntentFilter的匹配规则

1. IntentFilter中的过滤信息 启动Activity分为显式调用和隐式调用&#xff0c;前者没什么好讲的&#xff0c;后者需要Intent能够匹配目标组件的IntentFilter中所设置的过滤信息。包括action、category、data。 一个Activity中可以有多个IntentFilter&#xff0c;一个Intent…

Android中的Intent和Intent-filter总结

一&#xff0e;相关概念 &#xff08;一&#xff09;基本概念 Intent中文意思指”意图”,按照Android的设计理念,Android使用Intent来封装程序的”调用意图”,不管启动Activity、Service、BroadcastReceiver,Android都使用统一的Intent对象来封装这一”启动意图”。此外,Inten…

【intent-filter】AndroidManifest中<intent-filter>标签的 部分作用

这里写自定义目录标题 AndroidManifest.xmlIntent-filter 标签Intent-filter 标签中的常用元素Intent-filter 标签的作用Intent对象Intent显式启动活动窗口Intent隐式启动&#xff08;重要&#xff09; AndroidManifest.xml AndroidManifest.xml是安卓开发中主配置文件&#x…

IntentFilter功能简介

1.什么是IntentFilter &#xff1f; IntentFilter翻译成中文就是“意图过滤器”&#xff0c;主要用来过滤隐式意图。当用户进行一项操作的时候&#xff0c;Android系统会根据配置的 “意图过滤器” 来寻找可以响应该操作的组件&#xff0c;服务。 例如&#xff1a;当用户点击…

简述 IntentFilter(意图过滤器)

转载自&#xff1a;http://www.cnblogs.com/ywtk/p/4158103.html 侵删 1.什么是IntentFilter &#xff1f; IntentFilter翻译成中文就是“意图过滤器”&#xff0c;主要用来过滤隐式意图。当用户进行一项操作的时候&#xff0c;Android系统会根据配置的 “意图过滤器” 来寻找可…

IntentFilter详解

IntentFilter的意思就是意图过滤器&#xff0c;当我们隐式的启动系统组件的时候&#xff0c;就会根据IntentFilter来筛选出合适的进行启动。 如果组件的 IntentFilter 与 Intent 中的 IntentFilter 正好匹配&#xff0c;系统就会启动该组件&#xff0c;并把 Intent 传递给它。如…

WPF MessageBox简单用法

1、弹出小框 用法&#xff1a; if (MessageBox.Show("内容", "标题", MessageBoxButton.YesNo, MessageBoxImage.Information) MessageBoxResult.Yes){label1.Content "yes";}else{label1.Content "no";} 2、例子 if (MessageBo…

C# winform 中MessageBox用法大全(附效果图)

我们在程序中经常会用到MessageBox。 MessageBox.Show()共有21中重载方法。现将其常见用法总结如下&#xff1a; 1.MessageBox.Show(“Hello~~~~”); 最简单的&#xff0c;只显示提示信息。 2.MessageBox.Show(“There are something wrong!”,“ERROR”); 可以给消息框加上…

MessageBox使用详解

Messagebox是我们编写程序时都会用到的东西&#xff0c;我每次使用的时候都能让它显示出来&#xff0c;但是我并不了解它的结构&#xff0c;或者说语法&#xff0c;下面简单总结一下。 语法 Messagebox(<字符串>text,<字符串>title&#xff0c;messageboxbuttons&a…

C#中Messagebox的简单使用

MessageBox的使用方法比较多&#xff0c;下面对常用的几种做了简单的说明&#xff0c;详细的资料可参考微软官方文档。 Messagebox.Show(String) 显示具有指定文本的消息框。 private void button1_Click(object sender, EventArgs e){//Show(String).显示具有指定文本的消息…