文章目录
1、定义: Semaphore是一个计数信号量,用来控制同时访问某个特定资源的操作数量,主要用于实现资源的并发限制。
2、使用方法: 通过acquire()方法获取一个许可,如果无可用许可,acquire()将会阻塞直到有许可;release()方法释放许可。
3、主要用途: 用于控制资源的访问数量,如限制文件的同时读写数量。
📢 秒杀系统崩了后,我靠 Semaphore 救场:这 “并发闸机” 也太香了!
零、引入
凌晨 1 点,你正对着外卖软件纠结 “加不加蛋”,手机突然炸了 —— 领导的夺命连环 call,夹杂着运维小哥的嘶吼:“你负责的秒杀系统崩了!数据库连接池炸了,满屏‘connection refused’,几千用户卡在支付页骂娘!”

你吓得蛋都忘了加,火速登服务器看日志:秒杀活动才开 5 分钟,并发请求直接冲爆数据库连接池,100 个连接被瞬间抢光,后续请求全堵死。更要命的是,你上周还拍胸脯说 “并发防护稳如老狗”,现在脸疼得像被 JVM 的 GC 日志抽了耳光。
就在你准备写 “离职申请.pdf” 时,老架构师王哥叼着烤串发来消息:“慌啥?给系统装个‘并发闸机’啊!Semaphore 了解下,比你那‘裸奔并发’靠谱 10 倍!”

一、Semaphore:本质是 “带计数的并发闸机”
王哥边啃烤串边给你科普,比喻直接戳中痛点:“你把数据库连接池想象成小区停车场,总共就 100 个车位(连接)。秒杀时的并发请求就是抢车位的车,要是没人管,几千辆车同时冲进来,不仅堵死入口,还得把停车场栏杆撞烂(数据库崩了)。”
“Semaphore 就是停车场的保安,手里拿着‘车位许可’—— 初始化时定好有多少个许可(比如 100),每辆车进停车场(请求连接),就得先向保安要一个许可(acquire());车开出去(释放连接),再把许可还回去(release())。要是没许可了,后面的车就得排队等,直到有人还许可。”
他随手甩来一段修复代码,比你之前的 “裸奔并发” 简洁太多:
// 初始化Semaphore:100个许可(对应100个数据库连接)
// true表示公平模式——排队的请求按顺序拿许可,不插队
Semaphore semaphore = new Semaphore(100, true);
// 秒杀请求处理线程
new Thread(() -> {
Connection conn = null;
try {
// 1. 申请许可:没许可就排队等
semaphore.acquire();
System.out.println("拿到数据库连接许可,开始处理秒杀");
// 2. 获取数据库连接(此时连接池不会超配)
conn = dataSource.getConnection();
// 3. 处理秒杀逻辑:扣库存、生成订单
处理秒杀逻辑(conn);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 4. 释放连接和许可:不管成功失败,都要把许可还回去
if (conn != null) {
try { conn.close(); } catch (SQLException e) {}
}
semaphore.release();
System.out.println("释放连接和许可,下一个请进~");
}
}).start();
你盯着代码恍然大悟:“这不就是给并发请求‘限流’吗?之前我没加限制,所有请求一起冲数据库,不崩才怪!”
王哥发来个 “鄙视” 表情包:“总算开窍了!Semaphore 就干 3 件核心事,记牢了:
- 初始化定许可数:比如 100 个数据库连接就传 100,相当于‘闸机一次最多放 100 人’;
- acquire()拿许可:线程要执行临界操作(比如拿数据库连接),先申请许可,没许可就阻塞排队;
- release()还许可:线程干完活,必须把许可还回去,不然许可会越来越少,最后闸机直接卡死 —— 这比你忘关空调还败家!”
冷笑话预警:“为什么 Semaphore 不会让数据库崩?因为它懂‘限流 = 续命’,就像奶茶少糖 = 健康、代码注释 = 积德,程序员都懂这个道理,但总有人想‘多喝两口’‘少写两行’,最后把自己坑了!”
二、Semaphore 的 3 个核心用途:不止于限流
修复完秒杀系统,数据库连接池恢复正常,你终于能喘口气,王哥却没停下:“别以为 Semaphore 只能限流,这玩意儿用途广着呢,比如你上次吐槽的‘接口被爬虫刷爆’问题。”
你瞬间想起上个月的糗事:公司 API 被爬虫恶意刷了 10 万次请求,服务器 CPU 飙到 100%,被运维小哥追着骂 “没做接口防护”。
“那时候要是用 Semaphore,爬虫早就被拦在门外了。” 王哥给你举了 3 个实战场景,每个都戳中程序员痛点:
2.1 1. 接口限流(最常用):防止恶意请求 / 高并发冲垮服务

比如你的公开 API,想限制 “每秒最多 100 个请求”,用 Semaphore + 定时任务就能实现:
- 初始化 Semaphore (100);
- 每个 API 请求先acquire()拿许可,没许可就返回 “请求过于频繁”;
- 定时任务每秒调用semaphore.release(100),重置许可数 —— 相当于 “每秒放 100 个请求进来”。
王哥补了个脱口秀段子:“很多程序员做接口限流,就像给大门装个‘纸糊的栏杆’,爬虫一撞就碎;Semaphore 是‘钢筋水泥闸机’,还能调放行人数,比你那‘靠运气限流’靠谱多了 —— 运气这东西,在并发面前比null还不靠谱!”
2.2 资源池控制:避免资源超配(数据库连接池、线程池等)

除了数据库连接池,线程池也能用 Semaphore 控制并发。比如你有个线程池,核心线程数 20,但某个任务特别耗资源,想限制 “同时最多 5 个线程执行该任务”:
- 初始化 Semaphore (5);
- 任务执行前acquire(),执行后release()—— 哪怕线程池有 20 个线程,也只有 5 个能同时处理这个耗资源的任务,不会把 CPU / 内存跑满。
“这场景下,Semaphore 比ThreadPoolExecutor的maxPoolSize灵活多了。” 王哥解释,“线程池的最大线程数是‘全局限制’,Semaphore 是‘局部任务限制’—— 就像小区总共有 20 个停车位,但某栋楼的业主只能用 5 个,互不干扰,这才叫精细化管控!”
2.3. 并发任务协作:控制同时执行的任务数

比如你要处理 1000 个文件,每个文件处理都要调用第三方接口(第三方接口限制 “同时最多 8 个请求”):
- 初始化 Semaphore (8);
- 每个文件处理线程先acquire(),调用接口,处理完release();
- 这样不管你开多少个线程,同时调用第三方接口的请求永远是 8 个,不会触发对方的限流,也不会因为请求太多被拉黑。
这里插个程序员才懂的段子:“别把 Semaphore 和 CountDownLatch/CyclicBarrier 搞混啊!三者的区别就像:
- CountDownLatch:老板等所有员工下班锁门(等完成);
- CyclicBarrier:导游等游客凑齐发车(一起走);
- Semaphore:景区限流入园(控数量);
要是用 Semaphore 当 CountDownLatch 用,就像用闸机当门锁,纯属找骂 —— 上次小李就干过这事儿,代码 review 时被我们笑到退休!”
三、Semaphore 的 2 个关键细节:别踩这些坑
王哥啃完最后一口烤串,给你划重点:“Semaphore 不难,但这 2 个坑踩了就加班,记牢了:
3.1 公平模式 vs 非公平模式

- 公平模式(new Semaphore(100, true)):排队的线程按顺序拿许可,先到先得,不会插队 —— 适合秒杀、支付等需要公平性的场景,避免用户吐槽 “我先点的却没抢到”;
- 非公平模式(默认,new Semaphore(100)):线程申请许可时,会先尝试抢一把,抢不到再排队 —— 效率高,但可能导致某些线程一直抢不到(饥饿),适合对公平性要求不高的场景,比如日志打印。
“公平模式像排队买奶茶,谁先排谁先拿;非公平模式像加塞买奶茶,虽然快,但容易被人骂 —— 程序员写代码,既要效率,也要‘做人’啊!”
3.2 release()的坑:别多放也别不放
- 千万别忘放许可:一定要把release()放在finally里,不然线程抛异常后,许可没还回去,久而久之许可会被耗尽,所有线程都得排队等死 —— 这比忘关电脑还严重,电脑忘关顶多费电,许可忘还得加班;
- 别多放许可:release()默认放 1 个许可,要是放多了(比如release(2)),许可数会超过初始化值,相当于停车场保安多放了车进来,最后还是会堵死 —— 上次小张就干过这事儿,release(10)写成release(100),结果许可飙到 200,数据库连接池又炸了,被领导罚了半个月绩效。”
四、总结:记住这 3 句口诀,并发防护不翻车
王哥拍了拍你的肩膀(线上版):“Semaphore 核心就 3 句话,记牢了别再让我半夜救场:

- 要‘控并发数量’—— 用 Semaphore(闸机模式);
- 初始化定‘许可数’—— 等于你能承受的最大并发数(比如数据库连接数、接口 QPS);
- acquire()拿许可,release()还许可 —— 放finally里,别忘还、别多还。”
五、最后说句实在的

Java 的并发工具,就像生活里的 “防护装备”—— 没穿对,并发请求就是 “洪水猛兽”;穿对了,Semaphore 这种 “闸机” 就能帮你挡住 90% 的线上事故。
今天搞懂了 Semaphore,别忘了点赞让更多人避坑,赶紧关注我 —— 有空咱们扒一扒 “Phaser 怎么搞定动态增减线程的同步”,再教你怎么用 “Semaphore+CountDownLatch” 组合拳解决复杂并发场景,让你彻底告别 “并发加班” 的噩梦。
对了,把这篇分享给你那总写 “裸奔并发” 的同事,下次代码 review 时,你就能笑着指出他的错误,然后优雅地说:“兄弟,停车场保安的道理,你还没懂啊?”




1057

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



