惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
Cisco Talos Blog
Cisco Talos Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
V
V2EX
博客园 - 三生石上(FineUI控件)
Martin Fowler
Martin Fowler
WordPress大学
WordPress大学
D
Docker
S
SegmentFault 最新的问题
博客园 - 聂微东
美团技术团队
Apple Machine Learning Research
Apple Machine Learning Research
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
F
Fortinet All Blogs
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The GitHub Blog
The GitHub Blog
GbyAI
GbyAI
L
LangChain Blog
Vercel News
Vercel News
博客园 - 叶小钗
MongoDB | Blog
MongoDB | Blog
Stack Overflow Blog
Stack Overflow Blog
H
Help Net Security
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
The Cloudflare Blog
Engineering at Meta
Engineering at Meta
T
Threat Research - Cisco Blogs
T
Threatpost
Scott Helme
Scott Helme
T
Tailwind CSS Blog
Latest news
Latest news
Stack Overflow Blog
Stack Overflow Blog
Blog — PlanetScale
Blog — PlanetScale
The Register - Security
The Register - Security
罗磊的独立博客
P
Proofpoint News Feed
腾讯CDC
S
Schneier on Security
雷峰网
雷峰网
A
About on SuperTechFans
T
Tenable Blog
F
Full Disclosure
Cyberwarzone
Cyberwarzone
博客园_首页
有赞技术团队
有赞技术团队
K
Kaspersky official blog

任霏博客

我将关闭服务器:AI彻底掐死了奄奄一息的个人博客 - 博客文章 - 任霏的个人博客网站 Vibe Coding 实现本地模型 Token 自由 IntelliJ IDEA + LM Studio + LM Link + Continue 1Password涨价后,别急着退订1Password,这个操作能帮你省25% - 博客文章 - 任霏的个人博客网站 我,吃饱了撑的注册了个域名,Cloudflare账号没了,不建议将域名放在Cloudflare - 博客文章 - 任霏的个人博客网站 临时邮箱:保护隐私与免骚扰的新方式 - 博客文章 - 任霏的个人博客网站 价值4100万美元SOL被盗SwissBorg在Solana上遭遇安全事件超200万枚ETH排队退出质押 - 博客文章 - 任霏的个人博客网站 注意 Web3 钱包遭遇 NPM 超大规模供应链攻击投毒事件 - 博客文章 - 任霏的个人博客网站 我受到以太坊ERC-20假代币地址投毒攻击记录一下大家谨防上当受骗 - 博客文章 - 任霏的个人博客网站 在2025年使用显卡 NVIDIA RTX 2080 Ti 挖矿收益记录和分析还能不能挖矿 - 博客文章 - 任霏的个人博客网站 分享我是如何成功戒烟的经验(包含失败的经验) - 博客文章 - 任霏的个人博客网站 在 OpenWRT 中配置 PassWall2 插件的教程记录 - 博客文章 - 任霏的个人博客网站 Office Professional Plus 2019 VL 版下载与 KMS 激活 - 博客文章 - 任霏的个人博客网站 最近几天我的 CDN 流量受到来自电信[山东烟台]、[江苏扬州]两地家庭宽带的攻击 - 博客文章 - 任霏的个人博客网站 自建AI服务器使用PVE配置显卡直通虚拟机安装驱动、CUDA和cuDNN运行LLM大模型进行AI炼丹 - 博客文章 - 任霏的个人博客网站 各代英特尔Intel芯片组主板适配兼容的CPU和DDR内存数据统计 - 博客文章 - 任霏的个人博客网站 GitLab Global 国际站将在60天内删除中国大陆、香港、澳门地区的账号 - 博客文章 - 任霏的个人博客网站 Github Copilot Free 开放免费版所有人均可使用 OpenAI GPT-4o、Anthropic Claude 3.5 AI 代码生成服务 - 博客文章 - 任霏的个人博客网站 Cloudflare 更新了订阅协议明确禁止优选IP和搭建梯子的行为 - 博客文章 - 任霏的个人博客网站 Linux(systemd)手动离线安装二进制(binary)MairaDB数据库指定版本 - 博客文章 - 任霏的个人博客网站 流程引擎 Flowable/Activiti 无法启动报错:liquibase - Waiting for changelog lock.... - 博客文章 - 任霏的个人博客网站 Spring Boot 全局异常捕获 ControllerAdvice 无法捕获 过滤器(Filter)和拦截器(Interceptor)中的异常 - 博客文章 - 任霏的个人博客网站 Freenom 收回了全部免费域名(.tk/.cf/.gq/.ga/.ml) - 博客文章 - 任霏的个人博客网站 Alibaba Druid 数据库连接池 takeLast() AQS 死锁导致程序无响应 - 博客文章 - 任霏的个人博客网站 你的网站加入 HSTS preload 预加载列表了吗 - 博客文章 - 任霏的个人博客网站 我的博客网站接入使用 Cloudflare 的架构分享 - 博客文章 - 任霏的个人博客网站 在 Ubuntu 上的 Nginx 高并发配置实践 - 博客文章 - 任霏的个人博客网站 技术分析黑客敲诈勒索站长的新手法百度对此也无能为力 - 博客文章 - 任霏的个人博客网站 百度站长平台快速收录权限和sitemap提交权限被全部收回 - 博客文章 - 任霏的个人博客网站 极狐 GitLab 免费时代结束不升级付费账号将禁止登陆 - 博客文章 - 任霏的个人博客网站 免费.ml域名10年委托合同到期被马里共和国收回域名经营权 - 博客文章 - 任霏的个人博客网站 从极狐Gitlab看各种中间件技术选型 - 博客文章 - 任霏的个人博客网站 时隔十年首次收到 Google AdSense 的付款 - 博客文章 - 任霏的个人博客网站 ga域名被加蓬共和国从Freenom公司手中收回域名经营权 - 博客文章 - 任霏的个人博客网站 Freenom 被 Meta(Facebook) 起诉导致暂停 .tk/.ga/.ml/.cf/.gq 等新域名注册 - 博客文章 - 任霏的个人博客网站 生花妙笔信手来 – 基于 Amazon SageMaker 使用 Grounded-SAM 加速电商广告素材生成 [1] - 博客文章 - 任霏的个人博客网站 github.renfei.net 不再完整代理 Github 页面改为代理指定文件 - 博客文章 - 任霏的个人博客网站 优雅的源代码管理(三):本地优雅的使用 Git Rebase 变基 - 博客文章 - 任霏的个人博客网站 优雅的源代码管理(二):Git 的工作原理 - 博客文章 - 任霏的个人博客网站 优雅的源代码管理(一):版本控制系统 VCS(Version Control System)与软件配置管理 SCM(Software Configuration Management) - 博客文章 - 任霏的个人博客网站 ChatGPT 开发商 OpenAI 买下极品域名 AI.com - 博客文章 - 任霏的个人博客网站 火爆的 AI 人工智能 ChatGPT 国内注册教程、使用方式和收费标准 - 博客文章 - 任霏的个人博客网站 解决 SpringCloud 中 bootstrap.yml 不识别 @activatedProperties@ 参数 - 博客文章 - 任霏的个人博客网站 Cron表达式书写教程搞定Linux、Spring、Quartz的定时任务 - 博客文章 - 任霏的个人博客网站 阿里云香港可用区C发生史诗级故障 - 博客文章 - 任霏的个人博客网站 国产统信UOS服务器操作系统V20提供免费使用授权 - 博客文章 - 任霏的个人博客网站 开源站长推送工具效果评测推荐(百度/必应/谷歌) - 博客文章 - 任霏的个人博客网站 获取公网IP服务「ip.renfei.net」升级增加地理定位数据字段公示 - 博客文章 - 任霏的个人博客网站 腾讯微信成为 GitHub 秘钥扫描合作伙伴 - 博客文章 - 任霏的个人博客网站 免费设置亚马逊远程桌面 - 博客文章 - 任霏的个人博客网站 我关站了-个人备案核查要求关闭论坛系统 - 博客文章 - 任霏的个人博客网站 Linux 中 chmod 644、755、777权限的含义和使用方法 - 博客文章 - 任霏的个人博客网站 Spring Boot 3.0 发布啦但是我还是暂时放弃升级了 - 博客文章 - 任霏的个人博客网站 过时老旧电脑安装 Windows11 跳过 Win11 TPM、RAM、Secure Boot 最低系统要求限制检查 - 博客文章 - 任霏的个人博客网站 IT资讯网站 cnBeta.com 网站被关停域名已经被 clientHold - 博客文章 - 任霏的个人博客网站 当你 git push 时,极狐GitLab上发生了什么? - 博客文章 - 任霏的个人博客网站 昨晚接口又被日了,接口被疯狂调用的背后是人是鬼?是道德的沦丧还是人性的扭曲? - 博客文章 - 任霏的个人博客网站 Mac破解软件站MacWk下线破产了,我想分享Mac破解软件却不太敢 - 博客文章 - 任霏的个人博客网站 我和极狐GitLab的故事回顾 - 博客文章 - 任霏的个人博客网站 极狐 GitLab 可以集成石墨文档作为Wiki管理了 - 博客文章 - 任霏的个人博客网站 关于基于极狐 GitLab 的知识库探索思路 - 博客文章 - 任霏的个人博客网站 在极狐 Gitlab 流水线配置里设置镜像拉取策略 - 博客文章 - 任霏的个人博客网站 极狐 GitLab Markdown 可排序、可过滤的数据表格实现 - 博客文章 - 任霏的个人博客网站 极狐 GitLab Issue 统计的思路分享 - 博客文章 - 任霏的个人博客网站 把极狐 GitLab Runner 搬回家运行,指定专属 Runner - 博客文章 - 任霏的个人博客网站 给极狐 GitLab SaaS 安装百度统计代码统计仓库访问量 - 博客文章 - 任霏的个人博客网站 关于我在极狐GitLab造机器人这件事儿我觉得很酷 - 博客文章 - 任霏的个人博客网站 如何参与极狐GitLab开源项目成为贡献者 - 博客文章 - 任霏的个人博客网站 关于 Cloudflare R2 Storage 的使用体验测评和我的观点 - 博客文章 - 任霏的个人博客网站 西部数据(WD40NMZW) 4TB Elements(2060-800041-003)移动硬盘拆解记录 - 博客文章 - 任霏的个人博客网站 获取公网IP服务「ip.renfei.net」升级,支持根据请求头 Accept 响应不同格式数据 - 博客文章 - 任霏的个人博客网站 我站再次受到扫描攻击的公告 - 博客文章 - 任霏的个人博客网站 我站近期遭受到恶意不友好访问攻击公告 - 博客文章 - 任霏的个人博客网站 讨论下Java中的volatile和JMM(Java Memory Model)Java内存模型 - 博客文章 - 任霏的个人博客网站 Java中说的CAS(compare and swap)是个啥 - 博客文章 - 任霏的个人博客网站 草根站长利用极狐GitLab作为图床外链 JIHULAB 101 - 博客文章 - 任霏的个人博客网站 极狐GitLab上的Building风云 - 之API如此多娇 JIHULAB 101 极狐GitLab上的Building风云 - 之Security风云再起 JIHULAB 101 极狐GitLab上的Building风云 - 之Docker风云必胜 JIHULAB 101
大佬们在说的AQS,到底啥是个AQS(AbstractQueuedSynchronizer)同步队列 - 博客文章 - 任霏的个人博客网站
任霏 · 2022-04-08 · via 任霏博客

各位大佬应该听过很多大佬讲过AQS,到底啥是个AQS?根据名称 AbstractQueuedSynchronizer 我们可以猜到,这是一个抽象的排队同步器,每个汉字都认识,连到一起就不认识了,我们分开理解。

Abstract抽象

抽象排队同步器,中的Abstract抽象含义是它是个 Abstract 抽象类,所以 AbstractQueuedSynchronizer 并不直接提供服务,我个人的理解哈,Abstract抽象类就是写了一半的类,所以AQS其实是一个框架、一种解决方案,JDK作者根据他的思路写了一个解决方案,然后咱们可以继续补全他的类,实现自己的同步器。

Queued队列

抽象排队同步器,中的Queued队列含义是它依赖一个队列数据结构来实现,是一个先进先出(FIFO)的队列数据结构,当然也会配合相关同步器(信号量、事件等)来实现,内容有点多后面再说。

Synchronizer同步器

抽象排队同步器,中的Synchronizer同步器含义就是它是个同步器,嗯~跟没说一样,这里我写我自己的理解,不一定对哈,如果有错误欢迎指出,在多线程环境中各个线程被多个CPU随机挑选执行,所以几乎是乱序的执行,在某些场景我们需要让线程按顺序逐个执行,这个时候就用到同步器,让各个线程之间有顺序的执行,如果你是单线程的程序,那么这个AQS对单线程没有任何意义。

有啥用

首先,可以装X,哦不,是理解JDK大佬们的思想,加以学习,用虔诚的心膜拜各位大佬。基本在各个 Lock 锁中都能看到内部实现了一个 AQS 同步器,作为小白的我认为,在多线程中协调线程运行顺序,就可以使用同步器,虽然小白基本不会直接使用AQS,但是知道大佬们的使用姿势也是好的。

如果你想自定义同步器在实现AQS时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了。

上面我大致解释了一下AQS是啥,那接下来再看看内部都有啥,满世界的 volatile,咱们下期文章再谈,先看AQS。

volatile int state

这个注释上写的是:The synchronization state,同步状态,具体是啥含义?得看实现类的定义了,例如ReentrantLock可重入锁的实现,state是用来表示加锁次数,可重入就可多次加锁。

final class Node 内部类

这个 Node 是等待队列中的节点类,这里还需要提一下 CLH (Craig, Landin, and Hagersten) 锁,因为这是 CLH 锁的变体,CLH锁是一种是基于逻辑队列非线程饥饿的一种自旋公平锁,由于是 Craig、Landin 和 Hagersten三位大佬的发明,因此命名为CLH锁。AQS作为JUC的核心,而CLH则是AQS的基础。先继续看里面有什么:

volatile int waitStatus,节点等待状态,有5种值:

  • 0 当一个Node被初始化的时候的默认值

  • CANCELLED 为1,表示线程获取锁的请求已经取消了

  • CONDITION 为-2,表示节点在等待队列中,节点线程等待唤醒

  • PROPAGATE 为-3,当前线程处在SHARED情况下,该字段才会使用

  • SIGNAL 为-1,表示线程已经准备好了,就等资源释放了

volatile Thread thread,线程引用,这个就是装载的等待的线程了。

volatile Node prev,next,这个就是节点的前驱和后继的节点引用,这样就可以形成双向队列。

Node nextWaiter,链接到下一个等待条件的节点,或共享的特殊值。这个是Condition条件队列,先暂时放一放,后面再写条件队列和同步队列的转化。

volatile Node head

注意这里咱们已经从 Node 里出来了,这里是 AQS 里的头节点

volatile Node tail

这里是 AQS 里的尾节点

到这里基本介绍了AQS的结构,那到底怎么运行的呢,首先AQS只是个抽象类,只实现了等待队列的维护,所以这里只先说队列的运行过程,共享资源state的操作需要看具体实现类。

acquire获锁

在代码里看看获取锁的逻辑是啥,代码是:

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

执行顺序依次是:tryAcquire(arg)、addWaiter(Node.EXCLUSIVE)、acquireQueued,如果进 if 就执行 selfInterrupt(),不要着急挨个看看大佬们的操作。

尝试直接去获取资源

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

看第一个 tryAcquire 就懵逼了,大佬的操作呢?直接抛异常是什么鬼?还记得我上面说的吗,AQS只是个框架,实现了一半的类,这个就需要实现AQS的子类来实现,是否能重入,是否能加塞,子类自己去实现,所以没啥看的,继续下一个。

将该线程加入等待队列的尾部

addWaiter(Node)方法用于将当前线程加入到等待队列的队尾,并返回当前线程所在的结点,代码是:

private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

意思是把当前线程封装到Node里,然后判断尾节点不为空就将这个节点设置为尾节点,如果失败了就执行enq(node)入队。

enq(node)入队

通过CAS自旋加入队尾,CAS自旋后面的文章再讲,先看AQS的逻辑,代码是:

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

acquireQueued排队

经过上面tryAcquire尝试获锁addWaiter添加到队尾,现在该干嘛了?已经到队尾了,乖乖排队,等着被叫号重新唤起,我大概描述一下流程:

首先用try来处理线程中断,然后用 for 自旋不断循环尝试,拿到节点的前驱节点;

判断前驱节点是不是头节点,如果是的话就去tryAcquire尝试获取,成功的话通过setHead讲自己设置为头节点,讲前驱节点的后继引用设置为null,方便GC回收;

如果前驱节点不是头节点,自己可以去休息了,通过park()进入waiting状态,直到被unpark()。如果不可中断的情况下被中断了,那么会从park()中醒过来,自旋,发现拿不到资源,继续进入park()等待。

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

shouldParkAfterFailedAcquire检查状态,拿到前驱节点的waitStatus状态,看看前面排队的人有没有放弃,如果前驱节点放弃了就插队到前面,然后自己去休息吧。

parkAndCheckInterrupt,通过LockSupport.park()去休息,LockSupport.park()也放到后面文章再说,不要着急。

到这里获锁的AQS排队流程就走完了,现在开始释放流程。

release放锁

先看看代码里的逻辑:

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

先执行tryRelease,然后unparkSuccessor唤醒等待队列中下一个节点。

tryRelease,跟上面的获取锁一样,需要子类去实现。

unparkSuccessor,用于唤醒等待队列中下一个线程,主要是寻找下一个节点,如果下一个节点已经放弃了,就从后往前找队列里最前面的,然后唤醒节点。

这里为啥是从后往前找?而不是从前往后找?因为可能会有问题,设想这样的情况:

有一个节点正在调用addWaiter入队,将自己设置到队尾,执行完 compareAndSetTail(pred, node) 被踢出 CPU 挂起,这个时候你从前往后找,到最后的时候会发现 next 是 null,因为 pred.next = node; 这句还没执行!

以上是独占模式,还有共享模式,但我写不动了,在这里只写字吧,就不逐一去找代码了。

独占模式只有一个线程在工作,共享模式跟独占差不多,就多了一步操作,获得资源的线程会判断是否还有剩余的资源,如果还有剩余的资源会唤醒自己后面的兄弟一起来工作,这个资源数是 tryAcquireShared 返回的,也是子类自己实现的,我在这里就简单的写一下调用流程链:

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

tryAcquireShared,需要子类自己实现,但AQS已经把其返回值的语义定好了:负值代表获取失败;0代表获取成功,但没有剩余资源;正数表示获取成功,还有剩余资源

doAcquireShared,将当前线程加入等待队列尾休息

private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

这里跟独占式很像,主要不一样的是调用了 setHeadAndPropagate 这个方法,我们再看看这个方法:

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    setHead(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();
    }
}

里面又调用了 doReleaseShared 方法,什么意思呢?如果还有剩余量,继续唤醒下一个邻居线程,让自己的兄弟一起工作起来,这就是跟独占式最大的区别。

AQS就先写到这里吧,一旦扩展开到处是知识,我这个小白控制不住,下个文章写CAS吧,请关注我哦。