通过JMH框架 测试公平锁与非公平锁的性能(附测试代码和源码分析)

news2024/9/30 15:22:06

目录

先上测试代码:

上依赖:

输出结果:(注意不要debug运行,直接运行代码,否则报错)

源码-公平锁的 lock 方法:

源码-非公平锁的lock方法:

总结 非公平锁和公平锁的两处不同:

注解的说明:


先上测试代码:

package cn.net.cdsz.ccb.test;


import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Measurement(iterations = 10)
@Warmup(iterations = 5)
@State(Scope.Group)
public class FairAndUnfairTest {

    @Param({"1", "2"})
    private int type;

    private static Lock lock;

    @Setup
    public void setup() {
        switch (type) {
            case 1:
                lock = new ReentrantLock(true);
                break;
            case 2:
                //默认就是非公平锁
                lock = new ReentrantLock(false);
                break;
            default:
                throw new IllegalArgumentException("illegal lock type.");
        }
    }

    @Benchmark
    @GroupThreads(5)
    @Group("lock")
    public void put() {
        lock.lock();
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    private int randomIntValue() {
        return (int) Math.ceil(Math.random() * 600000);
    }

    @Benchmark
    @GroupThreads(5)
    @Group("lock")
    public Integer get() {
        lock.lock();
        try {
            Thread.sleep(10);
            return randomIntValue();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return 0;
    }

    public static void main(String[] args) throws RunnerException {
        Options opts = new OptionsBuilder()
                .include(FairAndUnfairTest.class.getSimpleName())
                .forks(1)
                .build();
        new Runner(opts).run();
    }
}

上依赖:

        <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-core</artifactId>
            <version>1.36</version>
        </dependency>
        
        <dependency>
            <groupId>org.openjdk.jmh</groupId>
            <artifactId>jmh-generator-annprocess</artifactId>
            <version>1.36</version>
            <scope>provided</scope>
        </dependency>

输出结果:(注意不要debug运行,直接运行代码,否则报错)

源码-公平锁的 lock 方法:

static final class FairSync extends Sync {
    final void lock() {
        acquire(1);
    }
    // AbstractQueuedSynchronizer.acquire(int arg)
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            // 1. 和非公平锁相比,这里多了一个判断:是否有线程在等待
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }
}

源码-非公平锁的lock方法:

static final class NonfairSync extends Sync {
    final void lock() {
        // 2. 和公平锁相比,这里会直接先进行一次CAS,成功就返回了
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    // AbstractQueuedSynchronizer.acquire(int arg)
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}
/**
 * Performs non-fair tryLock.  tryAcquire is implemented in
 * subclasses, but both need nonfair try for trylock method.
 */
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        //3.这里也是直接CAS,没有判断前面是否还有节点。
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

总结 非公平锁和公平锁的两处不同:

  1. 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。
  2. 非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则不去抢锁,乖乖排到后面。

公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。

相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导致在阻塞队列中的线程长期处于饥饿状态。
 

注解的说明:


@BenchmarkMode(Mode)

表示 JMH 进行 Benchmark 时所使用的模式。通常是测量的维度不同,或是测量的方式不同。目前JMH 共有四种模式:

  1. Throughput: 整体吞吐量,例如“1秒内可以执行多少次调用”。
  2. AverageTime: 调用的平均时间,例如“每次调用平均耗时xxx毫秒”。
  3. SampleTime: 随机取样,最后输出取样结果的分布,例如“99%的调用在xxx毫秒以内,99.99%的调用在xxx毫秒以内”
  4. SingleShotTime: 以上模式都是默认一次 iteration 是 1s,唯有 SingleShotTime 是只运行一次。往往同时把 warmup 次数设为0,用于测试冷启动时的性能。


@State(Scope.Thread)
State 用于声明某个类是一个“状态”,然后接受一个 Scope 参数用来表示该状态的共享范围。因为很多 benchmark 会需要一些表示状态的类,JMH 允许你把这些类以依赖注入的方式注入到 benchmark 函数里。Scope 主要分为三种。

  1. Thread: 该状态为每个线程独享。
  2. Benchmark: 该状态在所有线程间共享。
  3. Group:线程组共享一个示例,在测试方法上使用 @Group 设置线程组

@fork
进行 fork 的次数。如果 fork 数是2的话,则 JMH 会 fork 出两个线程来进行测试。

@Warmup:
Warmup 是指在实际进行 benchmark 前先进行预热的行为。为什么需要预热?因为 JVM 的 JIT 机制的存在,如果某个函数被调用多次之后,JVM 会尝试将其编译成为机器码从而提高执行速度。所以为了让 benchmark 的结果更加接近真实情况就需要进行预热。

@Measurement
进行 5 次微基准测试,也可用在测试方法上

@Benchmark
表示该方法是需要进行 benchmark 的对象,用法和 JUnit 的 @Test 类似。

@OutputTimeUnit
benchmark 结果所使用的时间单位。


@Param 可以用来指定某项参数的多种情况。特别适合用来测试一个函数在不同的参数输入的情况下的性能。


@Setup 会在执行 benchmark 之前被执行,正如其名,主要用于初始化


@TearDown 和 @Setup 相对的,会在所有 benchmark 执行结束以后执行,主要用于资源的回收等。


Iteration 是 JMH 进行测试的最小单位。在大部分模式下,一次 iteration 代表的是一秒,JMH 会在这一秒内不断调用需要 benchmark 的方法,然后根据模式对其采样,计算吞吐量,计算平均执行时间等。

include是benchmark 所在的类的名字,注意这里是使用正则表达式对所有类进行匹配的。
 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/429134.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

docker入门之一:docker基础概念与安装

1. Docker简单介绍 1.1. 什么是docker&#xff1f;1.2. Docker和传统虚拟机1.3. 为什么使用docker1.4. docker架构 2. Docker安装 2.1. docker版本命名2.2. docker安装2.3. docker卸载2.4. docker镜像加速器 1. Docker简单介绍 1.1. 什么是docker&#xff1f; google go语言…

24-Tomcat

目录 1.Tomcat是什么&#xff1f; 2.版本号 3.下载 4.目录介绍 4.1.bin目录 4.2.conf目录 4.3.logs目录 4.4.webapps目录 5.启动服务器 PS&#xff1a;解决Tomcat乱码问题 PS&#xff1a;Tomcat点击启动&#xff0c;控制台一闪而过&#xff0c;啥也没有解决方案 PS…

【花雕学AI】4月5日,ChatGPT中国财经背景分析:昨天沪指重返3300点,这说明了什么?

在这里插入图片描述 附录&#xff1a; 一、ChatGPT是一个可以和你聊天的人工智能程序&#xff0c;它可以用文字回答你的问题&#xff0c;也可以根据你的提示写出文章、歌词、代码等内容。ChatGPT是由一个叫OpenAI的机构开发的&#xff0c;它使用了一种叫做GPT的技术&…

TCP协议的相关特性(续)

TCP协议的相关特性&#x1f50e;滑动窗口&#x1f50e;流量控制&#x1f50e;拥塞控制&#x1f50e;延时应答&#x1f50e;捎带应答&#x1f50e;面向字节流(粘包问题)&#x1f50e;异常情况&#x1f50e;总结关于 确认应答 超时重传, 连接管理 请参考: 点击这里 &#x1f50e…

IT知识百科:什么是基站?

一、基站介绍 基站&#xff08;Base Station&#xff09;&#xff0c;也称为基站站点或基站设备&#xff0c;是无线通信网络中的关键设备之一。基站用于与移动设备&#xff08;如手机、无线网卡等&#xff09;进行通信和数据传输&#xff0c;实现无线通信覆盖。 二、基站的功…

基于Python机器学习、深度学习技术提升气象、海洋、水文领域实践应用

Python是功能强大、免费、开源&#xff0c;实现面向对象的编程语言&#xff0c;能够在不同操作系统和平台使用&#xff0c;简洁的语法和解释性语言使其成为理想的脚本语言。除了标准库&#xff0c;还有丰富的第三方库&#xff0c;Python在数据处理、科学计算、数学建模、数据挖…

Web服务器压力测试

Web服务器压力测试 使用WebBench去进行网站的压力测试 1、去github下载项目源码webbench 2、download到本地 3、将压缩包上传到虚拟机上 4、解压&#xff0c;使用命令&#xff1a;unzip 压缩包名 5、 cd WebBench-mask6、构建项目 makemake install7、上述动作完成后&…

【Vue3】如何用Vue CLI 创建一个Vue3的初始化项目

第一步、安装Vue Cli npm install -g vue/cli 安装成功后&#xff0c;就可以在命令行工具中&#xff0c;使用vue命令。 检测是否安装成功&#xff0c;可以用 vue -V 出现版本号&#xff0c;代表安装成功。 第二步、创建项目 为了方便管理项目&#xff0c;我先在 github 创建了…

队列实现图书信息管理(C语言)

文章目录Queue.hmain.cQueue.c用队列实现一个图书信息管理&#xff0c;这里放一下有哪些文件。&#xff08;ps&#xff1a;我之前写的是学生信息管理&#xff0c;但是有人说我们的作业是写图书&#xff0c;就该了下内容&#xff0c;没有改文件名&#xff09;队列是用链表实现的…

***大论文中插入Visio不失真方法:word插入viso图片方法

***大论文中插入Visio不失真方法&#xff1a;word插入viso图片方法1、可以直接导出emf2、如果利用emf导致字符间距过大&#xff0c;可以选择下面方式1、可以直接导出emf 导出emf方法&#xff1a; 打开visio --> 另存为 --> 选择emf格式文件 打开word --> 插入图片&a…

6 计时器(三)

6.4 输出比较演示** 演示1&#xff1a;PWM驱动呼吸灯** 函数解释&#xff1a; 输出比较单元&#xff08;掌握&#xff09; void TIM_OC1Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); void TIM_OC2Init(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCIni…

对Redis 的数据结构的更深刻理解

文章目录简单动态字符串SDS与C字符串的区别链表字典哈希算法 —— 添加新键值对的过程rehashrehash一般过程渐进式rehash渐进式rehash的详细步骤跳跃表实现整数集合intset升级步骤升级好处降级压缩列表 ziplistziplistnode连锁更新对象字符串对象列表对象哈希对象编码转换集合对…

RK356X 解除UVC摄像头预览分辨率1080P限制

平台 RK3566 Android 11 概述 UVC&#xff1a; USB video class&#xff08;又称为USB video device class or UVC&#xff09;就是USB device class视频产品在不需要安装任何的驱动程序下即插即用&#xff0c;包括摄像头、数字摄影机、模拟视频转换器、电视卡及静态视频相机…

详解C++中的命名空间(namespace)

个人主页&#xff1a;平行线也会相交 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 平行线也会相交 原创 收录于专栏【C之路】 目录C关键字(C98)命名冲突命名空间命名空间的定义局部域和全局域的关系命名空间域小结命名空间中可以定义哪些内容嵌套命名空间…

音视频开发常用分析工具介绍

综述 工欲善其事&#xff0c;必先利其器&#xff1b;兵马未到&#xff0c;粮草先行。 在音视频开发过程中&#xff0c;利用工具可以更方便、更直观、更快捷的分析音视频的数据&#xff0c;便于开发过程中分析、调试和解决问题。 现总结一些音视频开发过程中常用的分析工具。…

Android library native 代码不能调试解决方法汇总

android native开发会碰到native代码无法调试问题&#xff0c;而app主工程中的native代码是可以调试的。如果项目中存在多个module&#xff0c;那么在application模块中依赖library模块&#xff0c;并且library模块中有native代码的时候&#xff0c;当debug library模块中的这些…

如何高效获取数据价值?

导读&#xff1a;上一篇《大数据架构知识点详解&#xff1a;国产数据库创新、湖仓一体实践…》中我们阐述了四大体系之数据架构体系&#xff0c;解释了云原生大数据实践、国产数据库创新变革、湖仓一体落地实践、OLAP 发展趋势四个论坛的架构思路。 接下来是四大体系之二数据效…

【Linux】2、Linux 的基本命令

目录一、Linux 的目录结构二、Linux 命令三、ls 命令四、cd五、pwd六、特殊路径符七、mkdir八、touch九、cat 命令十、more十一、cp十二、mv十三、rm十四、which十五、find十六、grep十七、wc十八、管道符十九、echo二十、重定向符二十一、tail 命令一、Linux 的目录结构 &…

直播观看指南|SOFA 五周年,Live Long and Prosper!

SOFA 五周年活动将于 2023 年 4 月 15 日&#xff08;周六&#xff09;12:00 在北京朝阳区恒通国际创新园 C6 栋 C work 举行&#xff01;期待社区的小伙伴和对开源感兴趣的小伙伴们一起来现场玩哦&#xff5e;当然啦&#xff0c;不能来现场的小伙伴们也别担心&#xff0c;我们…

第二十一章 案例TodoList之新增数据

前一小节&#xff0c;我们已经完成了数据的动态展示&#xff0c;现在我们要完成数据的动态添加。如何添加呢&#xff1f;肯定是要通过Header组件来添加&#xff0c;但是Header组件如何将收集的任务数据&#xff0c;交给App组件并更新状态数据呢&#xff1f; 在Header组件中收集…