文章目录

📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌
📙 作者: 编程技术圈(哇哥面试陪跑)
👉 欢迎关注、分享、评论
✔️ 持续分享更多干货内容
🌐🌏🌎➕tcmeta, 欢迎沟通交流
📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌📌
零、引入
“并发访问又超了!” 王二盯着服务器监控直跺脚,他写的接口允许 10 个线程同时访问,但高峰期直接冲进来 20 个,数据库连接被占满,系统响应超时。
领导把需求甩给他:“用 Semaphore 控制并发数,最多 10 个线程同时进,再自己手写一个,搞懂底层!”
哇哥端着保温杯凑过来:“Semaphore 就是‘门禁通行权限’,比如公司门禁一次允许 10 个人进,用完权限就排队,用完再还回来。它也是基于 AQS 实现的,只不过是‘共享锁’—— 今天咱们先用 JDK 的 Semaphore 救急,再手写一个,让你彻底吃透 AQS 的共享锁逻辑!”
点赞 + 关注,跟着哇哥和王二,从使用到手写,精通 Semaphore,并发控制再也不慌!

一、先救急:JDK 的 Semaphore 怎么用?(5 分钟上手)
Semaphore 的核心是 “共享许可”,就像公司门禁有 10 个通行权限,员工刷一次卡拿一个权限,用完还回去 —— 对应代码里的acquire()拿许可,release()还许可。
👉 实战:用 Semaphore 控制接口并发数

运行结果:并发数被精准控制在 10
✨ 核心亮点
- 20 个线程抢 10 个许可,超过的自动排队,不会出现连接池被占满的情况;
- acquire()可中断、可超时(acquire(1, TimeUnit.SECONDS)),比 Lock 更灵活;
- 支持公平锁(new Semaphore(10, true)),按排队顺序拿许可,避免线程饥饿。
二、Semaphore 的底层:AQS 的 “共享锁” 逻辑

👉 核心源码
王二好奇 Semaphore 为啥能支持 “多人同时拿许可”,哇哥直接扒开源码:“Semaphore 是 AQS 的共享锁实现,和 ReentrantLock 的独占锁不同,它的tryAcquireShared方法返回剩余许可数,支持多个线程同时获锁。”


‼️ 用门禁场景讲透共享锁 vs 独占锁

📌 Semaphore 的 AQS 核心逻辑

Semaphore 的 AQS 子类重写了tryAcquireShared和tryReleaseShared,核心是 “许可数的增减”:
- tryAcquireShared:尝试拿许可,返回剩余许可数(>=0 代表成功,<0 代表失败);
- tryReleaseShared:尝试还许可,返回是否释放成功,成功后唤醒排队线程。
简化版源码逻辑(Semaphore 的 Sync 类)
// Semaphore的AQS子类(共享锁逻辑)
private static class Sync extends AbstractQueuedSynchronizer {
Sync(int permits) {
setState(permits); // state存许可总数
}
// 拿许可:共享锁核心方法
@Override
protected int tryAcquireShared(int acquires) {
for (;;) { // 死循环防虚假唤醒
int available = getState();
int remaining = available - acquires;
// 剩余许可不足,返回负数(失败)
if (remaining < 0 ||
// CAS修改许可数
compareAndSetState(available, remaining)) {
return remaining;
}
}
}
// 还许可:共享锁核心方法
@Override
protected boolean tryReleaseShared(int releases) {
for (;;) {
int available = getState();
int next = available + releases;
// 许可数不能超过总数(防止重复release)
if (next < available) {
throw new Error("许可数溢出");
}
// CAS修改许可数
if (compareAndSetState(available, next)) {
return true; // 释放成功,唤醒排队线程
}
}
}
}
“看到没?共享锁的核心是tryAcquireShared返回剩余许可数,” 哇哥强调,“AQS 会根据返回值判断是否需要排队 —— 如果剩余许可>=0,当前线程获锁成功;如果 < 0,就进 CLH 队列阻塞。”

三、手写 AQS 版 Semaphore:自己造 “门禁通行权限”

光看源码不够,哇哥带着王二手写一个基于 AQS 的 Semaphore,核心就是重写共享锁的两个方法,剩下的排队、唤醒全靠 AQS。
package cn.tcmeta.aqs;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
/**
* @author: laoren
* @description: 手写 Semaphore 完整代码
* @version: 1.0.0
*/
public class MySemaphore {
// 核心:AQS共享锁子类
private final Sync sync;
// 构造方法:指定许可数
public MySemaphore(int permits) {
if (permits <= 0) {
throw new IllegalArgumentException("许可数必须大于0");
}
this.sync = new Sync(permits);
}
// AQS共享锁逻辑
private static class Sync extends AbstractQueuedSynchronizer {
// 初始化许可数(state=permits)
Sync(int permits) {
setState(permits);
}
// 1. 拿许可(共享锁核心)
@Override
protected int tryAcquireShared(int acquires) {
for (; ; ) {
int available = getState();
int remaining = available - acquires;
// 剩余许可不足,返回负数(进队列排队)
if (remaining < 0 ||
// CAS修改许可数(线程安全)
compareAndSetState(available, remaining)) {
return remaining;
}
}
}
// 2. 还许可(共享锁核心)
@Override
protected boolean tryReleaseShared(int releases) {
for (; ; ) {
int available = getState();
int nextPermits = available + releases;
// 防止重复释放导致许可数溢出
if (nextPermits < available) {
throw new IllegalMonitorStateException("重复释放许可");
}
// CAS修改许可数
if (compareAndSetState(available, nextPermits)) {
return true; // 释放成功,AQS会唤醒排队线程
}
}
}
// 辅助方法:获取当前可用许可数
int getAvailablePermits() {
return getState();
}
}
// 拿1个许可(阻塞)
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// 拿1个许可(非阻塞,立即返回)
public boolean tryAcquire() {
return sync.tryAcquireShared(1) >= 0;
}
// 还1个许可
public void release() {
sync.releaseShared(1);
}
// 获取可用许可数
public int availablePermits() {
return sync.getAvailablePermits();
}
// 测试:用手写Semaphore控制并发
static void main() {
// 10个许可(最多10个线程同时访问)
MySemaphore semaphore = new MySemaphore(10);
ExecutorService pool = Executors.newFixedThreadPool(20);
for (int i = 1; i <= 20; i++) {
int userId = i;
pool.submit(() -> {
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + ":用户" + userId + "访问成功,可用许可=" + semaphore.availablePermits());
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
System.out.println(Thread.currentThread().getName() + ":用户" + userId + "访问完成,可用许可=" + semaphore.availablePermits());
}
});
}
pool.shutdown();
}
}

👉 核心亮点(哇哥划重点)
- 我们只重写了 AQS 的tryAcquireShared和tryReleaseShared,就实现了共享锁的核心逻辑;
- AQS利用LockSupport方法会自动处理 “抢锁 - 入队 - 阻塞”,不用我们写一行 LockSupport 代码;
- 支持中断:acquire方法可响应线程中断,比 JDK 早期的并发工具更灵活。

四、AQS 的终极价值:一套骨架,多种锁实现

王二写完手写 Semaphore,终于明白 AQS 的强大之处 —— 一套骨架能支撑不同类型的锁:
- 独占锁(ReentrantLock):重写tryAcquire/tryRelease,state=0/1;
- 共享锁(Semaphore):重写tryAcquireShared/tryReleaseShared,state = 剩余许可数;
- 倒计时锁(CountDownLatch):重写tryAcquireShared,state = 倒计时次数,减到 0 才允许获锁。
‼️ AQS 的核心设计思想:模板方法模式
AQS 用 “模板方法模式” 把锁的逻辑分成两部分:
- 模板方法:AQS 自带的acquire/release等方法,定义锁的整体流程;
- 钩子方法:留给子类重写的tryAcquire/tryAcquireShared等方法,定义具体的抢锁 / 放锁逻辑。
“这就像盖房子,AQS 帮你把钢筋骨架搭好,” 哇哥比喻,“你只需要根据需求砌砖墙(独占锁)或装玻璃(共享锁),不用从零开始盖。
五、AQS 全系列核心回顾
➡️ 小总结
- 顶层骨架(AQS):模板方法模式,提供排队、阻塞、唤醒的通用逻辑;
- 底层实现(LockSupport):AQS 的 “手脚”,实现线程的精准阻塞和唤醒;
- 上层应用(ReentrantLock/Semaphore):基于 AQS 的具体工具,重写钩子方法实现特定功能。
📢 哇哥的终极彩蛋
“面试时被问 AQS,别光说定义,” 哇哥传授面试技巧,“先讲‘总控台 + 对讲机’的比喻,再画 CLH 队列的结构,最后说‘我手写过基于 AQS 的 Semaphore’—— 拿出代码示例,面试官绝对对你刮目相看。记住:并发编程的核心是‘懂底层,会实战’,而不是背 API。”
✔️ 最后说句实在的
AQS 是 Java 并发的 “灵魂”,搞懂它,你就掌握了所有并发工具的底层逻辑。今天的手写 Semaphore,虽然是简化版,但核心逻辑和 JDK 源码一致 —— 能把这个写出来,你就已经超过了 80% 只会调 API 的程序员。
如果这篇帮你彻底吃透了 AQS,点赞 + 分享给你那还在死记硬背并发工具用法的同事!



170万+

被折叠的 条评论
为什么被折叠?



