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

article/2025/10/30 10:41:40

文章目录

    • 背景
    • System.currentTimeMillis()
    • 性能测试
      • 单线程测试
      • 多线程测试
    • 原因
    • 优化
    • 优化代码
      • 单线程测试
      • 多线程测试
    • 参考

背景

最近在看asyncTool源码发现了System.currentTimeMillis存在卡顿问题,所以就详细研究了下。具体如何呢?我们来看看

System.currentTimeMillis()

image-20220426100722451

jdk版本jdk11

可以看到该方法被@HotSpotIntrinsicCandidate注解修饰,代表使用HotSpot的实现代替JDK源码的实现方式,即基于CPU指令集。

方法的注释也说的很清楚

以毫秒为单位返回当前时间。 请注意,虽然返回值的时间单位是毫秒,但值的粒度取决于底层操作系统,并且可能更大。 例如,许多操作系统以几十毫秒为单位测量时间。
有关“计算机时间”和协调世界时 (UTC) 之间可能出现的细微差异的讨论,请参阅类 Date 的描述。
回报:
当前时间与 UTC 1970 年 1 月 1 日午夜之间的差异,以毫秒为单位。

说明该方法存在时间误差,有精度问题,大概误差在几十毫秒内,因操作系统而异

性能测试

测试机器:

操作系统:

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

单线程测试

  • 测试代码
	  @Testpublic void testSingleThread() {//测试一百次循环,每次循环调用1千万System.currentTimeMillis()次数for (int t = 0; t < 100; t++) {StopWatch stopWatch = new StopWatch();stopWatch.start();//获取一千万次时间for (int i = 0; i < 10000000; i++) {System.currentTimeMillis();}stopWatch.stop();long totalTimeMillis = stopWatch.getTotalTimeMillis();System.out.println(totalTimeMillis);}}
  • 测试结果

image-20220426101555942

其实可以看到消耗时间大概在134毫秒左右,但是最大值在198毫秒的,误差范围竟然高达50ms.

多线程测试

  • 测试代码
	@Testpublic void multiThread() throws Exception{// 测试执行1次StopWatch stopWatch = new StopWatch();stopWatch.start();System.currentTimeMillis();stopWatch.stop();long totalTimeNanos = stopWatch.getLastTaskTimeNanos();System.out.println(totalTimeNanos);System.out.println("=====================");//100个线程各执行一次CountDownLatch wait = new CountDownLatch(1);CountDownLatch threadLatch = new CountDownLatch(100);for (int i = 0; i < 100; i++) {new Thread(() -> {try {StopWatch watch = new StopWatch();//先阻塞住所有线程wait.await();watch.start();System.currentTimeMillis();watch.stop();System.out.println(watch.getTotalTimeNanos());} catch (InterruptedException e) {e.printStackTrace();} finally {threadLatch.countDown();}}).start();}//暂停1s保证线程创建完成TimeUnit.SECONDS.sleep(1);wait.countDown();threadLatch.await();}
  • 测试结果
    在这里插入图片描述
    可以看到测试结果平均在500ms左右,但是极端个别数据到达了11667ms,有点夸张

原因

单线程下产生延迟说明在系统底层上该线程和其他进程或线程产生了竞争,探究下hotspot中的实现:

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

以下是查询得知,涉及到汇编层面了。

  1. 调用gettimeofday()需要从用户态切换到内核态;
  2. gettimeofday()的表现受系统的计时器(时钟源)影响,在HPET计时器下性能尤其差;
  3. 系统只有一个全局时钟源,高并发或频繁访问会造成严重的争用。

优化

优化方式很简单,如果我们的误差允许在1ms内,那我们保证在1ms内只调用一次System.currentTimeMillis(),在1ms内的其他调用都直接使用这次调用的结果这样就大大避免了和其他线程抢夺资源的概率。也减少了线程上下文的切换,以及用户态到内核态的切换

优化代码

优化新增工具类SystemClock

/*** @author : wh* @date : 2022/4/26 22:42* @description:*/
public class SystemClock {private final int period;private final AtomicLong now;private static final String THREAD_NAME ="system.clock";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;}private void scheduleClockUpdating() {ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor(1, r -> {Thread thread = new Thread(r, THREAD_NAME);thread.setDaemon(true);return thread;});scheduler.scheduleAtFixedRate(() -> now.set(System.currentTimeMillis()), period, period, TimeUnit.MILLISECONDS);}private long currentTimeMillis() {return now.get();}/*** 用来替换原来的System.currentTimeMillis()*/public static long now() {return instance().currentTimeMillis();}
}

单线程测试

  • 测试代码
@Testpublic void testSingleThreadBySystemClock() {//测试一百次循环,每次循环调用1千万System.currentTimeMillis()次数for (int t = 0; t < 100; t++) {StopWatch stopWatch = new StopWatch();stopWatch.start();//获取一千万次时间for (int i = 0; i < 10000000; i++) {// 使用优化的代码SystemClock.now();}stopWatch.stop();long totalTimeMillis = stopWatch.getTotalTimeMillis();System.out.println(totalTimeMillis);}}
  • 运行结果

在这里插入图片描述

可以看到性能差距非常明显,都在5ms左右,相差了20多倍的效率

多线程测试

@Testpublic void multiThreadBySystemClock() throws Exception{// 测试执行1次StopWatch stopWatch = new StopWatch();stopWatch.start();SystemClock.now();stopWatch.stop();long totalTimeNanos = stopWatch.getLastTaskTimeNanos();System.out.println(totalTimeNanos);System.out.println("=====================");//100个线程各执行一次CountDownLatch wait = new CountDownLatch(1);CountDownLatch threadLatch = new CountDownLatch(100);for (int i = 0; i < 100; i++) {new Thread(() -> {try {StopWatch watch = new StopWatch();//先阻塞住所有线程wait.await();watch.start();SystemClock.now();watch.stop();System.out.println(watch.getTotalTimeNanos());} catch (InterruptedException e) {e.printStackTrace();} finally {threadLatch.countDown();}}).start();}//暂停1s保证线程创建完成TimeUnit.SECONDS.sleep(1);wait.countDown();threadLatch.await();}

在这里插入图片描述
整体都非常稳定,没有太大波动

参考

博客


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

相关文章

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).显示具有指定文本的消息…

MessageBoxA 和MessageBoxW

文章目录 第一个参数&#xff1a;第二个参数&#xff1a;第三个参数&#xff1a;第四个参数&#xff1a;补充代码实现 MessageBoxA(NULL, text, title, MB_OK) 第一个参数&#xff1a; HWND hWnd 传入一个句柄&#xff0c;这个窗口句柄代表的窗口就是这个消息窗口的所有者&…

C#中MessageBox()用法详解

简介&#xff1a; MessageBox&#xff08;&#xff09;功能是显示一个消息对话框&#xff0c;其中包含一个系统图标、 一组按钮和一个简短的特定于应用程序消息&#xff0c;如状态或错误的信息。 格式&#xff1a; MessageBox.Show&#xff08;string Text, string Caption,…