当前位置: 首页 > article >正文

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

文章目录

    • 背景
    • 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://www.lryc.cn/news/2416969.html

相关文章:

  • 串口(SerialPort)的使用
  • 常用正交表(正交法编写测试用例)
  • Redis——持久化之RDB
  • rhapsody软件_Rhapsody集成引擎之日志篇(一)
  • 撞库及其危害性
  • log4net的使用步骤
  • 2的n次方对照表
  • 【Android 四大组件之Service】一文吃透Service 服务
  • Linux 文件系统挂载 INITRAMFS 与 INITRD
  • PreparedStatement 用法
  • Nginx-基本安装
  • Windows Vista 系统中的用户帐户控制和UAC远程限制 设置
  • 总线概述及常见总线
  • 基于JAVAWeb+Tomcat+Mysql开发的微电影网站
  • Java 常用代码汇总-附源码
  • C语言之文件读写——fscanf(),fprintf()详解
  • mailbox机制实例介绍(一)
  • 线性代数——余子式
  • HCIP------ 网络类型 PPP协议和HDCL协议
  • TrueCrypt原理与系统开发
  • 数据增强 - Cutout、Random Erasing、Mixup、Cutmix
  • 探索 `mpvue-vant`: 微信小程序开发的新利器
  • mint-ui使用
  • JqGrid 各个属性、方法使用说明
  • 快速上手 Rook,入门云原生存储编排
  • xxxxxxxxxxxxxxxxxxxxx已转行
  • ROV简易组装说明
  • 还在为没有项目做发愁?这几个神级开源网站,都是FPGA/IC项目
  • segment fault异常及常见定位手段
  • 分享几个源码网站奉献给大家(持续更新中……)