一、类图
很简单,内部持有实现了AbstractQueuedSynchronizer的Sync,Sync为Semaphore的内部类,它没有什么特殊的成员变量,实现了基本的获取许可与释放的逻辑
二、许可的获取与释放
2.1 获取许可
//非公平锁
final int java.util.concurrent.Semaphore.Sync#nonfairTryAcquireShared(int acquires) {
for (;;) {
//获取可用的许可个数
int available = getState();
//减去需要请求的许可数
int remaining = available - acquires;
//如果许可小于零,那么认为获取许可失败,如果是正数,那么进行CAS改值
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
//公平锁
protected int java.util.concurrent.Semaphore.FairSync#tryAcquireShared(int acquires) {
for (;;) {
//先判断队列中是否存在其他waiting的节点,如果有,判定线程获取许可失败,保证公平性
if (hasQueuedPredecessors())
return -1;
//获取可用的许可个数
int available = getState();
//减去需要请求的许可数
int remaining = available - acquires;
//如果许可小于零,那么认为获取许可失败,如果是正数,那么进行CAS改值
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
当我们创建信号量的时候会设置一定数量的许可,线程获取到一个许可便会将许可总数减去1,然后返回剩余的许可数量,如果剩余的许可数量非负数,那么表示获取许可成功。
如果获取许可失败,需要加入到同步队列中等待其他线程释放许可唤醒自己
private void java.util.concurrent.locks.AbstractQueuedSynchronizer#doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//CAS添加节点到同步队列,具体的添加过程,请移步到第5节,此处不再赘述
//Node.SHARED用于表示当前线程所处模式为共享模式,是来竞争共享锁的
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
//如果前驱节点是空的头节点,那么尝试去获取许可
if (p == head) {
int r = tryAcquireShared(arg);
//如果获取许可成功,那么进入分支,准备替换头节点,并根据需要传播唤醒行为
if (r >= 0) {
//替换头节点为当前线程之前所在的节点,然后判断是需要传播唤醒行为
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//前驱节点必须为SIGNAL才允许挂起线程,如果不是SIGNAL,是CANCEL,那么先去掉当前节点连续的前驱CANCEL节点
//如果是其他的状态,比如0或者传播状态,那么先将前驱节点设置为SIGNAL,然后再尝试竞争许可,具体逻辑与这么做的原因
//请移步到第5节JDK并发锁之AQS与独占锁
if (shouldParkAfterFailedAcquire(p, node) &&
//挂起
parkAndCheckInterrupt())
//如果发生中断,将抛出中断异常
throw new InterruptedException();
}
} finally {
//如果许可获取失败(通常在设置有超时时间获取许可的方法中可能会导致failed = true),CANCEL节点,具体逻辑请移步第5节JDK并发锁之AQS与独占锁
if (failed)
cancelAcquire(node);
}
}
2.1 释放许可
protected final boolean java.util.concurrent.Semaphore.Sync#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;
}
}
许可的释放仅仅是将原来线程占用的许可还回去即可
三、总结
只要学习了第6节点读写锁,理解Semaphore是非常简单,所以对于Semaphore不会花太多的重复篇幅去分析。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?