Semaphore源码简单解读

news/2024/10/10 10:14:14

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.ryyt.cn/news/69698.html

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

相关文章

捕鱼船识别检测预警系统

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

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

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

希音面试: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\) 为偶数,那么答案就是…