一、acquire() 的工作流程

当调用 acquire() 方法时,实际调用的是 AQS 的 acquireSharedInterruptibly(1) 方法。以下是其详细工作流程:

// acquire() -> sync.acquireSharedInterruptibly(1),可中断
public final void acquireSharedInterruptibly(int arg) {
if (Thread.interrupted())
throw new InterruptedException();
// 尝试获取通行证,获取成功返回 >= 0的值
if (tryAcquireShared(arg) < 0)
// 获取许可证失败,进入阻塞
doAcquireSharedInterruptibly(arg);
}

1、Semaphore.acquire()

public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1); // 调用 AQS 的共享模式方法
}

2、AQS.acquireSharedInterruptibly(int arg)

public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException(); // 检查中断状态
}
if (tryAcquireShared(arg) < 0) { // 尝试获取许可
doAcquireSharedInterruptibly(arg); // 许可不足,加入队列
}
}

3、Semaphore.tryAcquireShared(int acquires)

protected int tryAcquireShared(int acquires) {
for (;;) {
int available = getState(); // 当前剩余许可数
int remaining = available - acquires; // 计算剩余许可
if (remaining < 0 || compareAndSetState(available, remaining)) {
return remaining; // 返回负数表示需要排队,非负数表示成功,需要通过 CAS 更新 state 变量(将available更新remaining)
}
}
}

4、doAcquireSharedInterruptibly(int arg),许可不足,加入队列

  private void doAcquireSharedInterruptibly(int arg) {
// 将调用 Semaphore.aquire 方法的线程,包装成 node 加入到 AQS 的阻塞队列中
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) {
// 成功后本线程出队(AQS), 所在 Node设置为 head
// r 表示【可用资源数】, 为 0 则不会继续传播
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// 不成功, 设置上一个节点 waitStatus = Node.SIGNAL, 下轮进入 park 阻塞
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
// 被打断后进入该逻辑
if (failed)
cancelAcquire(node);
}
}

5、setHeadAndPropagate(Node node, int propagate)

private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
// 设置自己为 head 节点
setHead(node);
// propagate 表示有【共享资源】(例如共享读锁或信号量)
// head waitStatus == Node.SIGNAL 或 Node.PROPAGATE,doReleaseShared 函数中设置的
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
// 如果是最后一个节点或者是等待共享读锁的节点,做一次唤醒
if (s == null || s.isShared())
doReleaseShared();
}
}

二、acquire() 的工作流程

当调用 acquire() 方法时,实际调用的是 AQS 的 acquireSharedInterruptibly(1) 方法。以下是其详细工作流程:

(1) 尝试获取许可

A、调用 tryAcquireShared(arg):

  • tryAcquireShared(arg) 是 AQS 的模板方法,由 Semaphore 实现

  • 在 Semaphore 中,tryAcquireShared(arg) 的逻辑是检查当前剩余许可数 state 是否足够

    • 如果 state >= 1,通过 int remaining = available - acquires; 操作将 state 减 1。

    • 如果 state < 1,返回负数,表示许可不足。

B、CAS 操作:

  • 使用 compareAndSetState(available, remaining) 方法原子性地更新 state 变量。

  • 如果 CAS 成功,表示线程成功获取许可;否则,重试或进入排队逻辑。

(2) 加入等待队列

A、创建节点并加入队列:

  • 如果 tryAcquireShared(arg) 返回负数(许可不足),线程会被封装为一个 Node 对象,加入 AQS 的 CLH 队列 尾部。

  • CLH 队列是一个双向链表,用于管理等待线程。

B、自旋与阻塞:

  • 线程在队列中自旋,尝试再次获取许可。

  • 如果仍然无法获取许可,线程通过 LockSupport.park() 方法被挂起,进入阻塞状态。

Semaphore.acquire()方法的底层原理的更多相关文章

  1. HashMap底层原理分析(put、get方法)

    1.HashMap底层原理分析(put.get方法) HashMap底层是通过数组加链表的结构来实现的.HashMap通过计算key的hashCode来计算hash值,只要hashCode一样,那ha ...

  2. KVO-基本使用方法-底层原理探究-自定义KVO-对容器类的监听

    书读百变,其义自见! 将KVO形式以代码实现呈现,通俗易懂,更容易掌握 :GitHub   -链接如果失效请自动搜索:https://github.com/henusjj/KVO_base 代码中有详 ...

  3. 红黑树规则,TreeSet原理,HashSet特点,什么是哈希值,HashSet底层原理,Map集合特点,Map集合遍历方法

    ==学习目标== 1.能够了解红黑树 2.能够掌握HashSet集合的特点以及使用(特点以及使用,哈希表数据结构) 3.能够掌握Map集合的特点以及使用(特点,常见方法,Map集合的遍历) 4.能够掌 ...

  4. 利用Redisson实现分布式锁及其底层原理解析

    Redis介绍 参考地址:https://blog.csdn.net/turbo_zone/article/details/83422215 redis是一个key-value存储系统.和Memcac ...

  5. 【分布式锁】07-Zookeeper实现分布式锁:Semaphore、读写锁实现原理

    前言 前面已经讲解了Zookeeper可重入锁的实现原理,自己对分布式锁也有了更深的认知. 我在公众号中发了一个疑问,相比于Redis来说,Zookeeper的实现方式要更好一些,即便Redis作者实 ...

  6. Java面试底层原理

    面试发现经常有些重复的面试问题,自己也应该学会记录下来,最好自己能做成笔记,在下一次面的时候说得有条不紊,深入具体,面试官想必也很开心.以下是我个人总结,请参考: HashSet底层原理:(问了大几率 ...

  7. Java8线程池ThreadPoolExecutor底层原理及其源码解析

    小侃一下 日常开发中, 或许不会直接new线程或线程池, 但这些线程相关的基础或思想是非常重要的, 参考林迪效应; 就算没有直接用到, 可能间接也用到了类似的思想或原理, 例如tomcat, jett ...

  8. 关于 ReentrantLock 中锁 lock() 和解锁 unlock() 的底层原理浅析

    关于 ReentrantLock 中锁 lock() 和解锁 unlock() 的底层原理浅析 如下代码,当我们在使用 ReentrantLock 进行加锁和解锁时,底层到底是如何帮助我们进行控制的啦 ...

  9. java并发AQS中应用:以acquire()方法为例来分析线程间的同步与协作

    谈到java中的并发,我们就避不开线程之间的同步和协作问题,谈到线程同步和协作我们就不能不谈谈jdk中提供的AbstractQueuedSynchronizer(翻译过来就是抽象的队列同步器)机制: ...

  10. 【T-SQL进阶】02.理解SQL查询的底层原理

    本系列[T-SQL]主要是针对T-SQL的总结. [T-SQL基础]01.单表查询-几道sql查询题 [T-SQL基础]02.联接查询 [T-SQL基础]03.子查询 [T-SQL基础]04.表表达式 ...

随机推荐

  1. canal源码分析简介-4

    7.0 driver模块  2018-11-10 22:30:19  6,053 4 driver,顾名思义为驱动.熟悉jdbc编程的同学都知道,当项目中需要操作数据库(oracle.sqlserve ...

  2. [学习笔记]最近公共祖先(LCA)之倍增算法

    1.定义 倍增法,顾名思义就是翻倍.它能够大大地优化时间复杂度.这个方法在很多算法中均有应用,例如求 LCA(最近公共祖先).(大雾) 2.框架 如下图,我们想找 \(4\) 和 \(8\) 的最近公 ...

  3. 重写equals()方法(idea生成的高效方法)

    equals 方法Object 类中的 equals 方法用于检测一个对象是否等于另外一个对象.在 Object 类中,这个方法将判断两个对象是否具有相同的引用.如果两个对象具有相同的引用, 它们一定 ...

  4. CRISP-DM的应用与理解

    本文分享自天翼云开发者社区<CRISP-DM的应用与理解>,作者:吴****嫄 CRISP-DM是一个数据挖掘项目规划的开放标准流程框架模型,主要分为业务理解.数据理解.数据准备.建模.评 ...

  5. 智算引领 AI启航,中国电信天翼云助推辽宁数智发展!

    近日,中国电信辽宁公司"智算引领 AI启航"新质生产力赋能辽宁新时代"六地"建设大会在沈阳圆满落幕.辽宁省工业和信息化厅,省国资委,省数据局,省农业农村厅,沈阳 ...

  6. nacos(一): 下载、运行与鉴权配置

    1.下载 nacao的官网地址是https://nacos.io/ 当前稳定版本是2.5.0,可以在官网下载,也可以在github上下载.其中,官网提供的下载地址是: https://download ...

  7. DeepSeek模型技术动态引行业关注,企业生产系统API迁移需审慎评估

    在当今数字化浪潮中,人工智能技术迅猛发展,各类模型如雨后春笋般涌现,而 DeepSeek 模型凭借其独特的优势,在人工智能领域迅速崭露头角,成为备受瞩目的新星. DeepSeek 模型由杭州深度求索人 ...

  8. DeepSeek本地性能调优

    技术背景 大模型性能调优是一个很复杂的工程,涉及到很多细节,如果真要对模型动刀子,还需要对模型和数据集本身有非常好的理解.这里我们仅仅考虑模型加载过程中,可以优化的一些参数配置.关于DeepSeek在 ...

  9. AWS - [01] 概述

    题记部分 001 || 概述   AWS,全称Amazon Web Services,是亚马逊公司旗下的云计算服务平台,自2006年起向全球用户提供广泛而深入的云计算服务.AWD是全球最全面.应用最广 ...

  10. Netty - [01] 概述

    题记部分 一.介绍 Netty 是由JBOSS提供的一个Java开源框架,现为Github上的独立项目. Netty是一个异步的.基于事件驱动的网络应用框架,用以快速开发高性能.高可靠性的网络I/O程 ...