【013】Java中的Semaphore及其主要用途


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 时,你就能笑着指出他的错误,然后优雅地说:“兄弟,停车场保安的道理,你还没懂啊?”
请添加图片描述
请添加图片描述
请添加图片描述
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值