AQS——条件变量的支持

简介: AQS——条件变量的支持

二、AQS对条件变量的支持


1、条件变量


之前的推文中,讲解过notify和wait是配合synchronized内置锁来实现线程间同步的,而条件变量的signal和await方法则是通过AQS来实现线程间同步的。


两者的不同之处在于:


synchronized同时只能与一个共享变量的notify或wait方法实现同步

AQS的一个锁可以对应多个条件变量的signal或await方法实现同步


共享变量我们知道,那啥是条件变量呢?来看看下面的例子:

package AQSLearn;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
 * Created by Zhong Mingyi on 2020/12/10.
 */
public class ConditionTest {
    private static ReentrantLock reentrantLock = new ReentrantLock();
    private static Condition condition = reentrantLock.newCondition();
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            reentrantLock.lock();
            try {
                System.out.println("thread1开始等待");
                condition.await();
                System.out.println("thread1被唤醒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                reentrantLock.unlock();
            }
        },"thread1").start();
        Thread.sleep(1000);
        new Thread(()->{
            reentrantLock.lock();
            System.out.println("thread2开始唤醒");
            condition.signal();
            System.out.println("thread2唤醒完成");
            reentrantLock.unlock();
        },"thread2").start();
    }
}

上述代码中:


  • 首先,创建了ReentrantLock对象,它是基于AQS实现的锁。
  • 然后,调用ReentrantLock对象的newCondition方法创建了一个Condition对象,这个Condition就是ReentrantLock锁对应的条件变量;一个ReentrantLock对象可以创建多个Condition对象。
  • 接下来,线程thread1调用reentrantLock.lock()方法获取独占锁,然后调用condition.await()方法阻塞挂起自己。
  • 线程thread2调用reentrantLock.lock()方法获取独占锁,然后调用condition.signal()方法唤醒了因为调用了该条件变量的await方法而阻塞的线程thread1。
  • 线程thread1从await方法处返回,继续执行后续逻辑。


2、条件队列


想知道内部的实现原理,来看看源码是怎么做的?


reentrantLock.newCondition()的作用其实是创建了一个在AQS内部声明的对象ConditionObject,由于ConditionObject是AQS的内部类,所以它可以访问AQS内部的变量和方法。在每个条件变量内部都维护了一个条件队列,用来存放调用条件变量的await方法时被阻塞的线程。


我们来看下condition.await();方法的实现。

  public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();//创建node节点,插入到条件队列队尾
            int savedState = fullyRelease(node);//释放当前线程获取的锁
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {//调用park方法阻塞当前线程
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

当线程调用条件变量的await 方法时(必须先调用锁的lock方法获取锁),在内部会构造一个类型为 Node.CONDITION的node节点,然后将该节点插入条件队列末尾,之后当前线程会释放获取的锁,并被阻塞挂起。这时候如果有其他线程调用lock方法尝试获取锁,就会有一个线程获取到锁,如果获取到锁的线程调用了条件变量的await方法,则该线程也会被放入条件变量的阻塞队列,然后释放获取到的锁,在await方法处阻塞。


再来看下condition.signal();方法的实现:

  public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }

当另外一个线程调用共享变量的signal方法时(必须先调用锁的lock方法获取锁),会把条件队列里队头的一个线程节点从条件队列里移除并放入AQS的阻塞队列里面,然后激活这个线程。


最后再来看看当一个线程调用条件变量的await方法被阻塞后,是如何进入条件队列的?

  private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }


其实学习了AQS队列的插入后,理解其条件队列的插入就更加简单了。首先是创建一个类型为Node.CONDITION的节点,然后单向地向条件队列尾部插入一个元素。


注意到:当多个线程同时调用lock方法获取锁时,只有一个线程获取到了锁,其他线程会被转换为Node节点插入到lock锁对应的AQS阻塞队列里面,并做自旋CAS尝试获取锁。


3、小结一下


当一个线程调用了条件变量的await方法后(必须在获取锁之后),会被转成Node节点放入到条件队列中,并释放获取的锁。


当另一个线程调用了条件变量的signal或signalAll方法时(必须在获取锁之后),会把条件队列里的一个或全部Node节点移动到AQS的阻塞队列中,等待时间获取锁。

相关文章
|
16天前
|
人工智能 自然语言处理 文字识别
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
Qwen3.7-Max是阿里云百炼面向智能体时代推出的新一代旗舰模型,对标GPT-5.5、Claude Opus 4.7等闭源旗舰。该模型支持百万级token上下文窗口,具备顶级推理能力、多模态搜索与视觉理解增强、流式输出低延迟响应等核心优势,覆盖编程、办公、长周期自主执行等复杂场景。同时支持OpenAI接口兼容,便于系统快速迁移。用户可通过Token Plan团队或节省计划等订阅方式灵活调用,适合企业级高要求场景使用。
6017 30
阿里云百炼Qwen3.7-Max简介:能力、优势、支持订阅计划参考
|
1天前
|
数据采集 人工智能 前端开发
让 Coding Agent 从黑盒到透明:阿里云 Agent 观测审计数据采集实践
AI Agent 规模化落地带来执行黑盒、行为难追溯、成本难度量三大难题。阿里云基于 OTel 标准,面向 Coding Agent、个人通用助理和框架型 Agent,推出 LoongSuite Pilot、插件及探针等无侵入采集方案,让 Agent 实现可看见、可分析、可审计、可治理。
572 135
|
11天前
|
存储 定位技术 数据库
CodeGraph 如何让 Claude Code减少 7 成工具调用?
CodeGraph 为 Coding Agent 提供本地代码知识图谱,把函数、类、调用链和框架路由提前整理成“项目地图”,减少盲目搜索和文件读取。它不是新 Agent,而是上下文基础设施,让 Agent 更快找到正确代码路径,平均减少 7 成工具调用。
1187 3
|
8天前
|
人工智能 安全 定位技术
CodeGraph深度解析 让Claude Code工具调用直降七成的核心原理与实操教程
如今以Claude Code为代表的AI编程智能体已经成为开发者日常编码、项目重构、漏洞修复的必备工具。但在长期使用过程中,几乎所有开发者都会遇到同一个明显痛点:AI虽然具备强大的代码生成与分析能力,却常常陷入盲目探索的循环中。
990 1
|
18天前
|
人工智能 自然语言处理 供应链
|
9天前
|
人工智能 弹性计算 安全
阿里云618活动时间、活动入口、优惠活动详细解读
2026年阿里云618创新加速季已全面开启,作为年度力度最大的云产品促销活动,本次大促覆盖轻量应用服务器、ECS云服务器、GPU云服务器、数据库、AI算力、安全服务、CDN等全品类产品,推出5亿元算力补贴、新用户限时秒杀、普惠满减、企业专享、免费试用、云大使返佣等多重福利,个人开发者、中小企业、AI团队均可享受专属低价。本文将系统梳理2026年阿里云618活动的完整时间节点、官方参与入口、各类优惠细则、使用规则、热门产品推荐及实操代码,帮助用户精准参与、高效省钱,以最低成本完成上云部署。
810 5
|
9天前
|
运维
欢迎报名|2026 Agentic AICon—智能体基础设施与AgentOps专场,邀您参会
欢迎报名|2026 Agentic AICon—智能体基础设施与AgentOps专场,邀您参会
1442 0