Semaphore源码简单解读

news/2024/10/10 10:11:05/文章来源:https://www.cnblogs.com/nammonco/p/18455735

Semaphore源码解读

注意,阅读本文需要了解AQS,AQS采用了模板设计模式。后续本人会完善这篇文章

Semaphore的方法

  1. acquire()
    阻塞获得一个许可,会阻塞,直到得到一个可用许可或被中断
    重载版本 acquire(n) :尝试获取n个许可
  2. acquireUninterruptibly()
    类acquire,但不可中断
  3. tryAcquire()
    非阻塞版本,只尝试一次,可被中断
    重载版本tryAcquire(n),tryAcquire(timeout,TimeUnit) 限时尝试,没抢到许可会等待直到得到许可或被中断 。tryAcquire(n,timeout,TimeUnit)
  4. release()
    释放一个许可。重载:release(n)
  5. drainPermits()
    当前线程剩余的许可
  6. hasQueuedThreads()
    判断是否队列中有等待许可的线程
  7. getQueuedLength()
    等待队列的长度

Semaphore总体框架

和ReentrantLock内部类似,Semaphore内部定义了一个抽象同步器Sync抽象类,它继承了AbstractQueuedSynchronizer。Sync有两个子类:NofairSync和FairSync,分别实现了非公平和公平两种版本的实现。Semaphore的各种acquire方法都是委托给Sync对象调用的。
Sema类

关于permit

Sync的构造方法。permit的本质是AQS的state

Sync(int permits) {// AQS的setState方法,setState(permits);}

Semaphore的 acquire方法

// Semaphore中
public void acquire() throws InterruptedException {sync.acquireSharedInterruptibly(1);}

AQS的模板方法acquireSharedInterruptibly,调用了tr·yAcquireShared钩子方法

//AQS
public final void acquireSharedInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);}

FairSync

FairSync重写了AQS的tryAcquireShared方法

protected int tryAcquireShared(int acquires) {for (;;) {//  若有前驱,则申请失败。保证申请者为第一个节点if (hasQueuedPredecessors())return -1;//  获取当前剩余许可量int available = getState();int remaining = available - acquires;// 若剩余许可<0,或cas成功,返回剩余量。// 为什么剩余量<0也要照样返回呢?if (remaining < 0 ||compareAndSetState(available, remaining))return remaining;}}

再看AQS的方法

//AQS
public final void acquireSharedInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);}

第二个if块有两种情况:

  1. 钩子方法tryAcquireShared返回负数
    即remaining<0,申请量大于许可量,则进入方法doAcquireSharedInterruptibly(arg)。由于钩子方法中使用短路或,remaining<0则不会cas交换
  2. remaining>0,cas成功,得到许可,则直接跳出方法,线程则继续下面的代码。在tryAcquireShared钩子方法中,进行过一次cas,若cas成功,返回remain,remain必然>=0,如果cas失败,则重新自旋,重复流程。

AQS的doAcquireSharedInterruptibly方法

private void doAcquireSharedInterruptibly(int arg)throws InterruptedException {//进入等待队列final Node node = addWaiter(Node.SHARED);boolean failed = true;try {// 死循环,for (;;) {//获得前驱节点final Node p = node.predecessor();// 若前驱为head,则进入申请方法// 也就是说,非队首节点不能进入该申请方法if (p == head) {// 调用钩子方法int r = tryAcquireShared(arg);//r>0则成功得到许可,跳出方法if (r >= 0) {//	该方法中,将当前节点设为head,并改变状态setHeadAndPropagate(node, r);p.next = null; // help GCfailed = false;return;}}//进行挂起判断if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())throw new InterruptedException();}} finally {if (failed)cancelAcquire(node);}}
private void setHeadAndPropagate(Node node, int propagate) {Node h = head; // Record old head for check belowsetHead(node);if (propagate > 0 || h == null || h.waitStatus < 0 ||(h = head) == null || h.waitStatus < 0) {Node s = node.next;if (s == null || s.isShared())doReleaseShared();}}

总结申请方法

Semaphore的 acquire调用了AQS的模板方法acquireSharedInterruptibly,acquireSharedInterruptibly进一步调用了钩子方法tryAcquireShared进行申请许可,在钩子方法中的死循环中,钩子方法退出循环的方式有两种:(1)remaining<0;(2)remaining<=0且cas成功。若计算出剩余许可remaining<0,则直接返回remaining,若remaining>=0,则进行CAS,若成功,返回r,该r也必然>=0。
在模板方法acquireSharedInterruptibly中,只有所需许可>可用许可时,才会进入方法doAcquireSharedInterruptibly,该方法封装线程节点入队,并且只有队首节点(前驱为head)才能够进行tryAcquireShared钩子调用,申请成功则它成为head,并且unpark后驱。

Acquire时的入队条件

  1. 模板的if中,调用tryAcquireShared钩子方法,返回负数(申请量大于可用量)。在FairSync中,队列中有其他节点时,会直接返回-1以进入队列
  2. 只要在钩子方法中,remaining>0,则会重复循环,直到成功或者r<0进入队列。
  3. NofairSync与FairSync相比,只是钩子方法去掉了队列中是否有结点的判断

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

捕鱼船识别检测预警系统

捕鱼船识别检测预警系统通过图像识别和数据分析技术,捕鱼船识别检测预警系统实时监测水域中的捕鱼船活动,系统利用河道两岸的摄像头,对捕鱼船的外形、大小、航行轨迹等进行检测和识别。捕鱼船识别检测预警系统一旦系统识别到违规捕捞行为,立即发出预警信号,并通知相关部门…

加油站抽烟烟火智能识别系统

加油站抽烟烟火智能识别系统利用摄像头和智能分析技术,加油站抽烟烟火智能识别系统实时监测加油站内的加油人员行为,加油站抽烟烟火智能识别系统通过图像识别和行为分析,识别出抽烟和燃放烟火的情况,并发出预警信号以提醒相关人员。加油站抽烟烟火智能识别系统能够实时监测…

希音面试:Redis脑裂,如何预防?你能解决吗?(看这篇就够了)

文章很长,且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 博客园版 为您奉上珍贵的学习资源 : 免费赠送 :《尼恩Java面试宝典》 持续更新+ 史上最全 + 面试必备 2000页+ 面试必备 + 大厂必备 +涨薪必备 免费赠送 :《尼恩技术圣经+高并发系列PDF》 ,帮你 实现技术自由,…

稀疏促进动态模态分解(SPDMD)详细介绍以及应用

在数据驱动分析领域,从复杂流体流动中提取有意义的模式一直是一个重大挑战。稀疏促进动态模态分解(Sparsity Promoting Dynamic Mode Decomposition, SPDMD)提供了一种有效方法,能够揭示最主要的特征,同时去除冗余信息,从而实现更高效和更具洞察力的分解。这种方法将动态…

CSP2024 前集训:多校A层冲刺NOIP2024模拟赛04

前言T1 签了。 T2 一眼后缀数组板子,但是复杂度是 \(O(nq\log(n))\) 的,极限数据本地 \(4\) 秒,但如果您会 \(O(n)\) 求后缀数组的话就直接过掉了,但赛时数据貌似纯随机,遂可以直接过掉,可以优化成 \(O(n^2\log(n)+nq)\) 或 \(O(n^2\log(n)+q)\) 的,赛时想打这个但是怕常…

抽象函数中图象变换的应用

一 问题引入 在高一学函数性质时,我们会遇到一些抽象函数的问题,先看两道例题: 【例1】已知函数\(f\left(2x+1\right)\)的定义域为\(\left[1,2\right]\),则函数\(f\left(4x+1\right)\)的定义域是 . 【例2】已知函数\(f\left(x\right)\)的定义域为\(\mathrm{R}\)…

P7394 「TOCO Round 1」History

操作树加二分,目前题解区没有这种做法。 发现操作一可逆,可以用操作树,操作三解决。 操作一单点修改没什么好说的。 接下来看操作二。令 \(fa_{x,k}\) 为 \(x\) 的 \(k\) 级祖先。 发现对于每个询问中,如果 \(y\) 为奇数那么答案为 \(0\)。如果 \(y\) 为偶数,那么答案就是…

php网站忘记后台密码忘记怎么办

如果你忘记了PHP网站后台的登录密码,可以通过以下几种方法来尝试恢复或重置密码:检查邮箱:如果在创建账户时设置了找回密码的功能,并且绑定了邮箱,可以先检查注册时使用的邮箱是否有找回密码的邮件。数据库直接修改:通过phpMyAdmin或其他数据库管理工具登录到MySQL数据库…

公司网站新闻图片修改方式

要修改公司网站上的新闻图片,通常可以按照以下步骤操作:备份原图:在进行任何修改之前,确保先备份原始图片文件,以防修改后不满意或出现其他问题。选择工具:根据需要修改的内容选择合适的图像编辑工具。常见的工具有Photoshop、GIMP(免费开源软件)或者在线编辑器如Canva…

网站提示数据库连接错误

当遇到网站提示数据库连接错误时,可以按照以下步骤进行排查和解决:检查数据库服务器状态:确认数据库服务是否正常运行。 使用命令行工具或管理界面尝试连接数据库。检查配置信息:核对应用中的数据库连接配置(如用户名、密码、主机地址、端口号)是否正确。 确保配置文件没…

宝塔面板忘记管理员用户名密码简单有效解决方法

宝塔面板是一款流行的服务器管理工具,如果忘记了管理员的用户名或密码,可以通过以下步骤来尝试恢复:登录宝塔官网获取帮助访问宝塔官网或者官方论坛,查找相关问题的解决方案。通过命令行重置密码使用SSH工具连接到服务器。 输入以下命令重置密码:bashpt-login按照提示操作…

网站修改后台登录密码

要修改网站后台的登录密码,通常可以通过以下几个步骤来实现:登录后台: 使用当前的用户名和密码登录到网站的管理后台。 进入用户设置: 在后台管理界面找到用户设置或个人信息设置的相关选项。 密码修改页面: 在用户设置中找到修改密码的选项或链接。 填写新密码: 按照提示输入…