Semaphore信号量源码解读与使用

news2024/11/16 7:49:12

🏷️个人主页:牵着猫散步的鼠鼠 

🏷️系列专栏:Java全栈-专栏

🏷️个人学习笔记,若有缺误,欢迎评论区指正 

目录

1. 前言

2. 什么是Semaphore?

3. Semaphore源码解读

 3.1 acquire():获取许可

3.2 release():释放许可

4. Semaphore的使用

5. 总结


1. 前言

在前面的文章我们讲解了CountDownLatch倒计时器的基本使用,本节我们继续来学习Java多线程编程中的一个工具类Semaphore信号量。

2. 什么是Semaphore?

Semaphore 是一个在多线程环境中用于控制对共享资源的访问的同步器(synchronizer),它是 Java 5 中引入的 java.util.concurrent(JUC)包的一部分。Semaphore 维护了一个许可集,线程在执行前必须从 Semaphore 获取一个许可。如果没有许可可用,线程将阻塞等待,直到其他线程释放许可。可以用来限制数据库连接数、限制服务器可处理请求数等。

3. Semaphore源码解读

我们跟进信号量的源码中浏览一圈,发现其实它内部主要的方法就2个:

// 初始共享资源数量
final Semaphore semaphore = new Semaphore(5);
// 获取1个许可
semaphore.acquire();
// 释放1个许可
semaphore.release();

 3.1 acquire():获取许可

/**
 *  获取1个许可证
 */
public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

跟进这个方法后,我们会发现其内部调用了AQS的一个final 方法acquireSharedInterruptibly(),

/**
 * 共享模式下获取许可证,获取成功则返回,失败则加入阻塞队列,挂起线程
 */
public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
      throw new InterruptedException();
        // 尝试获取许可证,arg为获取许可证个数,当可用许可证数减当前获取的许可证数结果小于0,则创建一个节点加入阻塞队列,挂起当前线程。
    if (tryAcquireShared(arg) < 0)
      doAcquireSharedInterruptibly(arg);
}

我们可以发现这个方法中又调用了tryAcquireShared(arg)方法,作为AQS中的钩子方法,这个方法的实现在Semaphore的两个静态内部类 FairSync(公平模式) 和 NonfairSync(非公平模式) 中。虽然这个方法在AQS中,但它作为钩子方法,最终的实现则回到了Semaphore的内部类中。

我们接下来继续查看在 FairSync(公平模式)中的tryAcquireShared方法

static final class FairSync extends Sync {
        private static final long serialVersionUID = 2014338818796000944L;

        FairSync(int permits) {
            super(permits);
        }

        protected int tryAcquireShared(int acquires) {
            for (;;) {
                if (hasQueuedPredecessors())
                    return -1;
                // 获取可用的许可数
                int available = getState();
                // 计算扣减后的许可数
                int remaining = available - acquires;
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }
    }

这里就是获取剩余的许可数值,然后计算扣减后的许可数值,如果扣减后的值小于0,就会直接返回扣减数(||短路或,前者满足就不用判断后者了)。否认就会执行CAS操作compareAndSetState(available, remaining)扣减许可数的值。

这里我们就可知,Semaphore信号量的许可数是存放在AQS中的state变量中的(AQS源码我后续应该也会发文讲解),然后通过CAS来修改state的值,保证了操作的原子性。

3.2 release():释放许可

同样跟入这个方法,里面用了AQS的releaseShared(),而在这个方法内也毫无疑问的用了tryReleaseShared(int arg)这个钩子方法,原理同上,不再冗释,需要注意的是释放共享锁(也就是增加许可数state的值)的同时也会唤醒同步队列中的一个线程。

// 释放一个许可证
public void release() {
    sync.releaseShared(1);
}

// 释放共享锁,同时会唤醒同步队列中的一个线程。
public final boolean releaseShared(int arg) {
    // 尝试释放共享锁(增加许可数)
    if (tryReleaseShared(arg)) {
      //唤醒同步队列中的一个线程
      doReleaseShared();
      return true;
    }
    return false;
}
protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                int current = getState();
                int next = current + releases;
                // 溢出情况判断
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
                if (compareAndSetState(current, next))
                    return true;
            }
      }

4. Semaphore的使用

OK,讲到这里,把信号量中主要的方法解释完了,我们来写一个小demo感受一下它的使用:

public class Test {
    private final Semaphore semaphore;

    /*构造一个令牌*/
    public Test(int acq){
        this.semaphore= new Semaphore(acq);
    }
    public void useSemaphore(){
        try {
            semaphore.acquire();
            // 使用资源
            System.out.println("资源使用 " + Thread.currentThread().getName());
            Thread.sleep(1000); // 模拟资源使用时间
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release();
            System.out.println("资源释放 " + Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) {
        Test test = new Test(3);
        for (int i = 0; i < 5; i++) {
            new Thread(test::useSemaphore).start();
        }
    }
}

输出:

资源使用 Thread-0
资源使用 Thread-1
资源使用 Thread-3
资源释放 Thread-0
资源使用 Thread-2
资源使用 Thread-4
资源释放 Thread-1
资源释放 Thread-3
资源释放 Thread-4
资源释放 Thread-2

5. 总结

Semaphore是基于AQS和CAS操作实现的共享锁,利用AQS中被volatile修饰的变量state来代表许可证数(permit),当用户调用acquire()方法来获取许可证数时,他会计算出本次取出操作后的许可证数,如果小于0,那么就会挂起等待,如果大于0,也就是许可证数还够用,那么就会使用CAS操作修改许可证数。当调用release()方法释放许可证数时,会唤醒等待的线程去获取共享锁。

如果本文对你有帮助,点个小赞吧,后续有时间就多出出这方面的源码解读

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

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

相关文章

yml文件解析

.yml 后缀的文件可以有多个application.yml # 项目相关配置 用于 RuoYiConfig.java ruoyi:# 名称name: RuoYi# 版本version: 3.8.5# 版权年份copyrightYear: 2023# 实例演示开关demoEnabled: true# 文件路径 示例&#xff08; Windows配置D:/ruoyi/uploadPath&#xff0c;Lin…

SQLite的知名用户(二十九)

返回&#xff1a;SQLite—系列文章目录 上一篇:SQLite作为应用程序文件格式&#xff08;二十八&#xff09; 下一篇:SQLite FTS5 扩展&#xff08;三十&#xff09; SQLite被数以百万计的应用程序使用 从字面上看&#xff0c;有数十亿次部署。 SQLite 是 当今世界。 下面…

C语言指针加法/减法

减法运算 指针的减法运算公式&#xff1a;就是两个指针的内存差值 / 指向的数据类型的内存空间大小 如果改变了指针类型指向的数据类型&#xff0c;对指针进行 - 运算&#xff0c;其新指向的位置只和转换后的数据类型有关 如果是char &#xff0c; 则指针指向变化1byte 如果…

【微服务】spring读取配置文件多种方式深入详解

目录 一、前言 二、java配置文件介绍 2.1 java配置文件产生原因 2.2 项目使用配置文件好处 2.3 springboot项目配置文件的必要性 2.4 微服务架构下配置文件使用场景 三、java读取配置文件常用方法 3.1 使用Properties类读取配置文件 3.1.1 使用getResourceAsStream读取…

【人工智能基础】知识表示和专家系统

目录 一、知识 知识 产生式表示法 产生式表示形式 确定的事实性知识的产生式表示 不确定的事实性知识的产生式表示 确定的规则知识的产生式表示 不确定的规则知识的产生式表示 产生式系统构成 优点 缺点 语义网络 优点 缺点 框架 框架的一般结构 框架的继承 优…

如何30天快速掌握键盘盲打

失业后在家备考公务员&#xff0c;发现了自己不正确的打字方式&#xff0c;决定每天抽出一点时间练习打字。在抖音上看到一些高手的飞速盲打键盘后&#xff0c;觉得使用正确的指法打字是很必要的。 练习打字&#xff0c;掌握正确的键盘指法十分关键。 练习打字的第一步是找到…

AJAX——ajax原理

1.XMLHttpRequest 定义&#xff1a;XMLHttpRequest&#xff08;XHR&#xff09;对象用于与服务器交互。通过XMLHttpRequest可以在不刷新页面的情况下请求特定URL&#xff0c;获取数据。这允许网页在不影响用户操作的情况下&#xff0c;更新页面的局部内容。XMLHttpRequest在AJA…

大话设计模式-里氏代换原则

里氏代换原则&#xff08;Liskov Substitution Principle&#xff0c;LSP&#xff09; 概念 里氏代换原则是面向对象设计的基本原则之一&#xff0c;由美国计算机科学家芭芭拉利斯科夫&#xff08;Barbara Liskov&#xff09;提出。这个原则定义了子类型之间的关系&#xff0…

MySQL之binlog归档日志

binlog&#xff08;二进制归档日志&#xff09; binlog 二进制日志记录保存所有执行过的修改操作语句&#xff0c;不保存查询操作。如果 MySQL 服务意外停止&#xff0c;可通过二进制日志文件排查&#xff0c;用户操作或表结构操作&#xff0c;从而来恢复数据库数据。启动 bin…

[BJDCTF 2020]encode

脱壳 rc4 自定义base64 异或

Make/Makefile详解

文章目录 make/MakefilemakeMakefile时间 make/Makefile Linux项目自动化构建工具。makefile定义了一系列的规则来指定&#xff0c;哪些文件需要先编译&#xff0c;哪些文件需要后编译&#xff0c;哪些文件需要重新编译&#xff0c;甚至进行更复杂的功能。makefile带来的好处就…

神经网络中的神经元和激活函数介绍

文章目录 1、什么是人工神经网络 2、什么是神经元 3、什么是激活函数 线性激活函数 Sigmoid激活函数 双曲正切激活函数 修正线性单元&#xff08;ReLU&#xff09;激活函数 Leaky ReLU激活函数 Softmax激活函数 1、什么是人工神经网络 神经网络能够利用多层神经元学习复杂的模…

回顾发展史,WEB组态终于可以搭建业务系统了!

一、WEB组态融合了传统工业组态和现代数据大屏 组态软件在工业控制领域有着二三十年的历史&#xff0c;比较知名的国内有组态王、国外有MCGS/WinCC等&#xff0c;用来搭建上位机监控画面&#xff0c;通常配合PLC使用&#xff0c;通过配置的方式&#xff0c;不需要编码&#xf…

线性表的链式存储(双向循环链表)

文章目录 前言一、双向循环链表是什么&#xff1f;二、双向循环链表的意义三、双向循环链表的操作实现总结 前言 T_T此专栏用于记录数据结构及算法的&#xff08;痛苦&#xff09;学习历程&#xff0c;便于日后复习&#xff08;这种事情不要啊&#xff09;。所用教材为《数据结…

micro-app的css样式隔离

手写微前端micro-app-CSS隔离 子应用的CSS可能会对基座应用或者其他子应用产生的影响 首先现在我们把react页面放入到vue2的页面大家也能看到一些问题了&#xff0c;在react中的index.css中对body的一些css样式&#xff0c;已经影响了基座应用的css。 为了看的更明显&#x…

【Linux】git

大家好&#xff0c;我是苏貝&#xff0c;本篇博客带大家了解Linux的编译器-gcc/g&#xff0c;如果你觉得我写的还不错的话&#xff0c;可以给我一个赞&#x1f44d;吗&#xff0c;感谢❤️ 目录 1.安装git2.在gitee上创建仓库3.首次配置4.下载仓库到本地5.三板斧6.git log7.gi…

STL::string简单介绍

目录 1、什么是STL STL6大组件:仿函数、算法、容器、空间配置器、迭代器、配接器 推荐文档&#xff08;必须学会看文档&#xff09; 2、string常用接口 a、初始化 1、什么是STL 标准模板库 STL&#xff08;Standard Template Library&#xff09;&#xff0c;主要是数据结构…

【C++程序员的自我修炼】初识模板

云收天彩色 木叶落秋声 目录 函数模板 函数模板的实现 函数模板的实例化 模板参数的匹配原则 参数模板推不出来的情况 类模板 类模板的定义格式 类模板的实例化 契子 ✨ 我们在学 C语言 的时候应该都写过交换两个数的函数 swap 吧 当时我们只是写了 int 类型&#xff0c;那…

sherpa + ncnn 离线语音识别

目录结构 前言音视频格式转为wavsherpa-ncnn编译LinuxWindowswindows编译中遇到的问题问题“nmake -? failed with: no such file or directory”编译失败原因 成功编译截图 可执行程序说明模型下载语言识别测试LinuxWindows 参考文献 前言 小编需要实现离线音视频语言部分识…

#STM32F407VET6(天空星)标准库和HAL驱动ILI9341

一、驱动方式&#xff1a;软件SPI&#xff0c;屏幕像素320*240 二、标准库含触摸&#xff0c;HAL库不含触摸 三、立创参考的文档 【立创天空星ST32F407VET6】模块移植手册 - 飞书云文档 (feishu.cn)https://lceda001.feishu.cn/wiki/MFNpw4STVi5ImikkcH1clWrlnqb 四、引脚分…