AQS 共享模式(Shared):`tryAcquireShared` 的返回值语义、传播唤醒(propagate)与 `Semaphore/CountDownLatch` 独占模式Exclusive更像“厕所只有一个坑位”共享模式Shared更像“会议室有 N 个座位/门禁要等人到齐”。这篇只讲共享模式最核心、也最容易被问懵的 3 件事你必须记住的 3 句话面试直出共享模式不是“多人同时拿锁”这么简单它的核心是tryAcquireShared用返回值表达“拿到没 后面的人还要不要继续放行”。releaseShared的关键不只是改state更重要的是通过“传播唤醒propagate”把唤醒链条往后推避免只叫醒一个导致队列不前进。Semaphore/CountDownLatch/ReadLock都是共享模式的典型它们只是在定义state的业务含义许可数、倒计数、读锁计数。1. 共享模式到底在解决什么问题独占模式成功者只有一个所以释放时通常唤醒一个后继就够了。共享模式可能允许多个线程成功例如 Semaphore 有 10 个许可可能同时放行多个如果还按“独占那套只唤醒一个”的思路做队列会出现这种现象明明还有剩余资源许可/读锁可用但队列后面的线程迟迟醒不过来业务表现为吞吐被压住、RT 抖动共享模式必须回答两个问题当前线程拿没拿到拿到之后后面还要不要继续放行放行多少这个“继续放行”的语义就是返回值设计 propagate 的来源。2.tryAcquireShared的返回值0 / 正数 / 负数到底表示什么这是共享模式面试的分水岭。AQS 约定你按这个口径讲就稳返回值 0获取失败需要入队/等待返回值 0获取成功但不需要/不应该继续传播唤醒例如资源刚好用完返回值 0获取成功并且还可能允许后续线程继续获取需要考虑传播唤醒一句话心智模型负数没拿到0我拿到了但后面别指望我再放行正数我拿到了而且后面可能还能继续放行为什么要区分 0 和 正数因为共享场景下“拿到”不等于“资源还剩”。如果资源还有剩、但不传播后面线程可能一直睡着如果资源没剩还传播会造成无意义唤醒惊群/空转。3. 传播唤醒propagate为什么共享模式不能只唤醒一个你可以用这个例子讲Semaphore 初始许可3队列里排了很多线程释放一次后如果只唤醒 1 个线程可能只消费掉 1 个许可剩余许可没人来拿队列后面还在 park所以共享释放的典型目标是只要还“有机会让更多线程成功”就要把唤醒继续往后推这就是 propagate 的直觉含义把“你可以醒来试试”的信号沿着队列往后传注意传播唤醒不是无脑唤醒所有。它仍然遵循“谁醒来谁再tryAcquireShared”如果资源不足醒来也会失败并再次 park它解决的是避免资源可用但线程不醒队列不前进4. 三个典型同步器如何映射到共享模式4.1Semaphorestate许可数语义tryAcquireShared(n)CAS 减少许可数成功表示拿到 n 个许可返回值失败许可不足返回负数成功后如果还剩许可返回正数提示可传播常见坑忘记 release表现为许可越来越少最终所有线程都在parking把 Semaphore 当锁用许可数设置不当会造成“看似互斥但实际并发”或“吞吐被压死”4.2CountDownLatchstate计数器语义await()是acquireShared只有当 state0 才成功返回正数或 0 取决于实现否则失败入队等待countDown()是releaseSharedCAS 将 state 递减当递减到 0 时需要传播唤醒所有等待者让队列快速前进常见坑复用 latchCountDownLatch 一旦到 0 不能重置要用 CyclicBarrier 或重新 newcountDown 次数不匹配导致 await 永远等不到线上就是一堆 WAITING4.3ReentrantReadWriteLock的读锁state读计数以及写锁相关位直觉读锁允许多个线程同时持有所以是共享模式写锁需要独占所以写锁走独占模式共享模式在读锁里的典型表现只要不存在写锁占用/写锁强优先的阻塞条件读线程可以继续成功读释放要能推动后继继续竞争尤其是读多写少场景读写锁的位运算细节你不需要在这里背公式面试除非你写过源码否则讲清“共享 vs 独占 写饥饿/读饥饿取舍”就够了。5. 线上排查怎么判断“共享模式的队列没前进/传播不够”你在线上会看到两类很典型的信号资源理论上可用但吞吐上不去例如 Semaphore 许可没耗尽但线程还是在等jstack 大量WAITING (parking)堆栈出现AbstractQueuedSynchronizer.acquireSharedAbstractQueuedSynchronizer.doAcquireSharedLockSupport.park排查步骤按这个顺序做比较稳先确认同步器的“资源语义”是否真的可用Semaphore当前许可是否被泄漏未 releaseLatchcountDown 是否真的能到 0再确认是否存在“持锁者/慢路径”例如临界区里包含 IO/慢 SQL导致释放频率低一个很实用的判断如果线程都卡在 acquireShared且一直没有任何线程从 await/permit 返回优先怀疑业务路径漏了release/countDown计数/许可配置错误6. 自测清单你要能顺口讲出来Q共享模式和独占模式最大的区别A独占同一时刻只允许一个成功共享可能允许多个成功所以需要返回值语义 传播唤醒来推动队列前进。QtryAcquireShared的返回值为什么要分0/0/0A0表示失败要排队0表示成功但资源可能刚好用完不建议再传播0表示成功且可能还能放行后续需要传播。Q传播唤醒是不是等于唤醒所有A不是。它是“把唤醒链往后推”让后继有机会醒来竞争能否成功仍取决于tryAcquireShared。QCountDownLatch 为什么不能复用A它的 state 只会减到 0AQS 的语义是“到 0 全放行”不会再回到初始值。7. 30 秒背诵稿AQS 共享模式的核心是tryAcquireShared的返回值语义小于 0 表示失败需要排队等于 0 表示成功但不建议继续传播大于 0 表示成功且可能还能继续放行后续线程。共享释放不仅是更新 state更关键是通过传播唤醒把队列向后推进避免资源可用但线程一直 park。Semaphore 把 state 当许可数CountDownLatch 把 state 当倒计数读锁把 state 当读计数它们都是复用 AQS 共享模板并定义 state 的含义。