AQS:Java并发包里的“包租公”,管理着你的锁和通行证!

AQS:Java并发包里的"包租公",管理着你的锁和通行证!🔑

一个曾试图手写公平锁,结果写出"史诗级BUG"的Java码农。现在,我选择相信AQS。🙏

朋友们,今天我们来聊聊Java并发世界里那位低调但无处不在的"扫地僧" ------AbstractQueuedSynchronizer,江湖人称 AQS

如果你用过ReentrantLockSemaphoreCountDownLatch,觉得它们好用又神奇,那你已经享受了AQS的"福报"。因为这些神器的核心引擎,都是这位"包租公"在默默驱动。

一、AQS是啥?一个"万能同步器模板"!

想象一下,你想在小区里搞个共享单车停放点,规则是:

  1. 车位有限,比如5个。
  2. 有车的人才能停(获取资源),没车的人得排队等。
  3. 有人骑走一辆(释放资源),队头的哥们才能进去停。

你会怎么实现这个规则?

自己手写?搞个volatile int表示剩余车位,再用个List<Thread>当等待队列?然后处理各种边界情况、并发竞争、线程唤醒... 光是想想,头发已经开始掉了。💇

AQS站出来说:"别折腾了!这套'资源管理+排队'的通用流程,我帮你实现了!"

AQS的核心就是一个同步器框架,它为你准备好了两样东西:

  1. 一个int state :表示"资源数量"。比如state=5表示有5个车位。你可以自定义state的含义(是锁的重入次数?还是信号量的许可数?随你定)。
  2. 一个FIFO双向链表队列 :这就是那个"等待队列"。没抢到资源的线程,乖乖进去排队睡觉(park),等资源释放了再被唤醒。

说白了,AQS就是一个"包租公"

  • 他手里有个小本本(state),记录着还有几套房(资源)可租。
  • 门口有个排队机(CLH队列),没租到房的客户(线程)就在这排队等。
  • 他定义了租房、退房的基本流程,但具体的"租房资格审核规则"(比如,是"先到先得"还是"VIP优先"),由你这个"房东"来定

二、为啥要用它?因为"重复造轮子,轮子砸自己脚"!

在AQS出现之前,每个大神想实现一个新锁或同步工具,都得从头写一遍:状态维护 + 线程排队 + 阻塞唤醒 + 处理竞争。不仅容易出错,而且代码复杂到亲妈都不认识。

AQS解决了什么?

它把同步器最复杂、最容易出错的公共部分 (线程排队、阻塞、唤醒、公平性调度)抽出来,用精妙的、经过千锤百炼的代码实现好了。你只需要关注业务核心逻辑如何定义"获取资源"和"释放资源"的规则。

这就像:

  • 以前:你想开餐馆,得自己盖楼、拉水电、装修、招厨师、设计菜单... 累死。
  • 现在(用AQS) :AQS给了你一个精装修的商铺模板 ,水电管网、排烟消防全做好了。你只需要决定卖火锅还是卖烧烤(定义资源规则) ,然后摆上桌椅厨具就能开业!🚀

三、怎么用?继承它,然后"填空"!

AQS是抽象类,你用它的方式就是继承它,并实现几个"钩子方法"。最重要的是这两个:

  • protected boolean tryAcquire(int arg)尝试获取资源 。规则你定!成功返回true,失败返回false
  • protected boolean tryRelease(int arg)尝试释放资源 。规则你定!成功返回true

还有用于共享模式的tryAcquireSharedtryReleaseShared

AQS的骨架伪代码(极度简化版):

scss 复制代码
// 你继承的这个"包租公"内部,有一个核心的"获取资源"流程
public final void acquire(int arg) {
    // 1. 先问问你定的规则:现在能拿到资源吗?(调用你重写的tryAcquire)
    if (!tryAcquire(arg)) {
        // 2. 没拿到?那把自己加到等待队列尾部
        Node node = addWaiter(Node.EXCLUSIVE);
        // 3. 进入一个循环:不断检查自己是不是队头,是就再tryAcquire一下,不是就安心睡觉(park)
        for (;;) {
            if (node.prev == head && tryAcquire(arg)) { // 是队头,再试试
                setHead(node);
                return;
            }
            // 不是队头或者还没抢到,就找地方安心睡觉,等前驱节点唤醒
            parkAndCheckInterrupt();
        }
    }
}
// release也类似,释放成功后,会去唤醒队列中合适的线程

看明白了吗? ​ 复杂的"排队-检查-睡觉-唤醒"循环,AQS帮你包了!你只需要告诉它: "什么情况下算获取成功?"(tryAcquire的逻辑)。

四、实战:用AQS手搓一个"非公平锁"

我们来实现一个最简陋的MyLock,体验一下当"房东"的感觉。

java 复制代码
// 注意:这是演示版,工业级请用ReentrantLock!
public class MyLock {
    // 请出我们的"包租公"!
    private final Sync sync = new Sync();
    
    // 核心:继承AQS,定义我们的规则
    private static class Sync extends AbstractQueuedSynchronizer {
        // 规则1: 获取锁。state为0表示锁空闲,为1表示被占用。
        @Override
        protected boolean tryAcquire(int acquires) {
            // 经典CAS操作:如果state是0,就把它改成1,表示我抢到了!
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread()); // 记录锁被谁占了
                return true; // 成功!
            }
            return false; // 抢失败了
        }
        
        // 规则2: 释放锁
        @Override
        protected boolean tryRelease(int releases) {
            if (getExclusiveOwnerThread() != Thread.currentThread()) {
                throw new IllegalMonitorStateException(); // 不是锁的持有者,想啥呢?
            }
            setExclusiveOwnerThread(null); // 清空持有者
            setState(0); // 资源恢复空闲
            return true;
        }
        
        // 额外:这算是个锁吧,所以提供个条件查询
        protected boolean isHeldExclusively() {
            return getExclusiveOwnerThread() == Thread.currentThread();
        }
    }
    
    // 对外的锁接口
    public void lock() {
        sync.acquire(1); // 调用AQS定义好的"获取"模板流程!
    }
    public void unlock() {
        sync.release(1); // 调用AQS定义好的"释放"模板流程!
    }
}

看到了吗? ​ 我们只写了核心的资源竞争规则 (用CAS抢state),而线程排队、阻塞、唤醒这些脏活累活,全交给sync.acquire()sync.release()了,这都是AQS父类里现成的!

五、工作中的注意事项:别把"包租公"用成"背锅侠"!

  1. "别直接继承AQS!"(在绝大多数情况下)

    AQS是给并发大师们造轮子用的 (比如Doug Lea本人用来造ReentrantLock)。我们普通码农,**99.9%的情况应该直接用ReentrantLockSemaphoreCountDownLatch**​ 这些现成的、完美的轮子。别为了炫技去手写一个自定义同步器,除非你是框架开发者。

  2. 理解"公平"与"非公平"

    • 非公平锁 (默认):tryAcquire时,不管有没有人排队,新来的线程直接尝试"插队"抢资源。吞吐量高,但可能"饿死"排队久的线程。
    • 公平锁tryAcquire时,会先检查队列里有没有人在等,有的话就乖乖去排队。更讲武德,但性能略有损耗。
    • 大部分场景,用默认的非公平锁就好,性能至上。
  3. 小心"状态"的设计

    AQS里的state是个int,别乱用。设计自定义同步器时,要精确规划state的每一位表示什么。比如ReentrantLockstate表示重入次数,Semaphore用它表示剩余许可数。别把state当成万能垃圾桶,什么都往里塞。

六、该怎么恰当地用?站在巨人的肩膀上!

  1. 日常开发 :忘记你需要继承AQS这件事。直接、熟练地使用ReentrantLockSemaphoreCountDownLatchCyclicBarrierReentrantReadWriteLock。它们都是基于AQS构建的、久经考验的"瑞士军刀"。
  2. 阅读源码 :当你想深入理解这些工具的原理时,再去啃它们的源码。你会发现,它们内部都有一个Sync类继承自AQS,然后分别实现了公平/非公平的tryAcquiretryRelease。这是学习并发设计的绝佳教材。
  3. 知识储备 :知道AQS的存在和其"模板方法"的设计模式,能让你在面试时侃侃而谈,在工作中遇到诡异并发问题时,多一个深入分析的视角。 "哦,这个锁可能是AQS队列挂住了" ------能说出这句话,你已经超过很多同行了。

结语

AQS是Java并发王国的基石,是隐藏在java.util.concurrent包众多神器背后的"无名英雄"。它把最脏、最累、最易错的"线程排队管理"活揽了下来,只留给你一个简单的接口去定义"资源竞争规则"。

所以,让我们对这些底层大师们保持敬意。下次当你流畅地用lock.lock()lock.unlock()时,记得心里默念一句: "谢谢AQS包租公!" ​ 🎩

(现在,你可以愉快地去用ReentrantLock了,把继承AQS的冲动,留给真正的框架作者们吧。)

相关推荐
掘金者阿豪1 小时前
Joplin笔记告别局域网高效办公就靠cpolar
前端·后端
小钻风33661 小时前
Java 8 流式编程
java·开发语言·windows
肯戳加勾1 小时前
JAVA最常见的装箱/拆箱坑
java·后端
Memory_荒年1 小时前
ReentrantLock:AQS家的“锁二代”,但比 synchronized 更会“来事儿”
java·后端
巫山老妖1 小时前
OpenClaw 心跳机制实战:让 AI Agent 24 小时不停自主运行
java·前端
没有bug.的程序员1 小时前
低代码平台后端引擎:元数据驱动架构、插件化内核与 Java 扩展机制
java·低代码·架构·插件化·元数据·扩展机制
武子康2 小时前
大数据-246 离线数仓 - 电商分析 Hive 拉链表实战:初始化、每日增量更新、回滚脚本与错误排查
大数据·后端·apache hive
懈尘2 小时前
【实战分享】智慧养老系统核心模块设计 —— 健康监测与自动紧急呼叫
java·后端·websocket·mysql·springboot·livekit
亚马逊云开发者2 小时前
写 Prompt 让 AI 出代码?Kiro 说你该先写 Spec
java