news 2026/5/16 5:46:22

一站式了解Semaphore的基本用法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一站式了解Semaphore的基本用法

引言

我们今天一起来了解一下JUC的同步工具类-Semaphore的基本用法。

什么是Semaphore(信号量)

Semaphore (信号量)是 java.util.concurrent 包下非常有用的一个并发工具类。你可以把它理解为用于控制同时访问特定资源的线程数量的工具。它维护了一组“许可”(permits),线程在访问受保护资源前必须先获取一个许可,使用完后再释放该许可。

场景引入

想象一个只有 5 个车位的停车场(共享资源):

  1. Permits (许可证/令牌):初始化时,Semaphore 拥有 5 个令牌。
  2. Acquire (获取):车辆(线程)进入时,先领 1 个令牌。如果没有令牌了(计数为0),车辆就得在门口排队(阻塞)。
  3. Release (释放):车辆离开时,归还 1 个令牌。此时如果有排队的车辆,就会唤醒其中一个进入。

这就是Semaphore做的事,控制在一个时间段内可以访问特定资源的线程数量。

基本概念

  • 许可(Permit):Semaphore 内部维护一个计数器,表示当前可用的许可数量。
  • acquire():尝试获取一个或多个许可。如果没有足够许可,线程会被阻塞,直到有足够许可可用。
  • release():释放一个或多个许可,增加可用许可数量,可能唤醒等待中的线程。

底层原理

Semaphore 内部基于AQS (AbstractQueuedSynchronizer)实现。

  • 它使用 AQS 的 state 变量来存储当前的许可证数量。
  • acquire() 操作是对 state 进行 CAS 减法。
  • release() 操作是对 state 进行 CAS 加法。

常用方法

下面是Semaphore的常用方法。

方法

描述

场景

new Semaphore(int permits)

创建非公平信号量(默认)。

吞吐量优先

new Semaphore(int permits, boolean fair)

创建公平信号量(FIFO)。

避免线程饥饿

acquire()

获取1个许可,若无则阻塞。响应中断。

必须拿到的场景

acquire(int n)

一次获取 n 个许可。

批量资源

tryAcquire()

尝试获取,成功返回 true,失败返回 false,不阻塞

快速失败/降级处理

tryAcquire(long timeout, TimeUnit unit)

带超时的尝试获取。

避免长时间死等

release()

释放1个许可。

务必在 finally 中调用

void release(int permits)

释放指定数量许可。

务必在 finally 中调用

使用例子

这是最典型的使用场景:限流 (Rate Limiting)资源池控制

java

import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; public class SemaphoreExample { // 定义一个只能允许3个线程同时访问的信号量 private static final Semaphore semaphore = new Semaphore(3); public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(10); // 模拟10个请求同时涌入 for (int i = 0; i < 10; i++) { final int threadNum = i; executor.execute(() -> { try { // 1. 获取许可 (如果拿不到,这里会阻塞) // 也可以使用 tryAcquire() 来进行非阻塞尝试 semaphore.acquire(); System.out.println("线程 " + threadNum + " 拿到了令牌,正在处理业务..."); // 模拟业务耗时 TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { // 2. 关键:一定要在 finally 中释放许可! System.out.println("线程 " + threadNum + " 处理完毕,归还令牌 ---"); semaphore.release(); } }); } executor.shutdown(); } }

一般使用场景有这些,特别是框架特别喜欢用:

  • 数据库连接池(限制最大连接数)
  • 线程池任务提交限流
  • 文件读写并发控制
  • 服务接口的 QPS 限流

平时使用的坑你要注意

场景 A:公平性 (Fairness)

new Semaphore(3, true) 会保证等待最久的线程最先拿到令牌。

  • 优点:避免线程饥饿。
  • 缺点:性能较差(因为要维护严格的队列顺序,增加了上下文切换开销)。通常后端高并发场景默认使用非公平模式以换取吞吐量。

场景 B:初始化为 0

Semaphore 不一定要初始化为正数。如果你初始化为 new Semaphore(0):

  • 线程 A 调用 acquire() 会立刻阻塞。
  • 直到线程 B 调用 release(),线程 A 才会继续执行。
  • 用途:这种模式类似 CountDownLatch 或 Exchanger,用于线程间的单次信号通知

常见坑

  1. 忘记 Release:如果在异常发生前没有释放,或者没写在 finally 块中,在这个 JVM 进程重启前,那个令牌就永久丢失了(类似内存泄漏),最终导致所有线程阻塞。所以千万要记住,写release在finally块!!
  2. Release 滥用:release() 只是简单地将计数器 +1。如果你在没有 acquire 的情况下错误地调用了多次 release(),信号量的许可证数量会超过初始值(比如变成 5+1 = 6),导致限流失效。

和平时的ReentrantLock / synchronized 的区别

使用用途和目的是它们最大的区别。

特性

synchronized / ReentrantLock

Semaphore

控制粒度

一次只允许一个线程进入临界区

可允许多个线程(≤许可数)同时进入

是否可重入

是(ReentrantLock)

否(许可不属于特定线程)

主要用途

互斥访问

资源池、限流、并发控制

那么使用自定义计时器+锁+唤醒等待机制也是可以实现Semaphore一样的功能,但是没必要,还容易出错。不如直接使用Semaphore。

总结

Semaphore就是用来限制同时访问统一资源的工具。

除了平时开发使用到Semaphore,我们平时面试做有关于多线程的算法题也有可能用到噢,所以还是非常有必要熟悉Semaphore的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/3 4:42:37

【课程设计/毕业设计】基于Springboot+Vue的野生动物园智能化管理系统野生动物园科普教育基于springboot的西安秦岭野生动物园智能化管理系统【附源码、数据库、万字文档】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/5/16 5:46:21

Java毕设项目:基于springboot的西安秦岭野生动物园智能化管理系统(源码+文档,讲解、调试运行,定制等)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/5/8 6:21:14

Zephyr学习之spi flash驱动记录(w25q128)

前言 1.环境搭建 Zephyr开发环境搭建记录(Clion) 2. 使用的开发板为正点原子的探索者&#xff1a;mcu使用的是STM32F407ZGT6 3. 本篇参考示例spi_flash 4. 开发环境使用Clion 参考说明 W25Q128部分原理图 W25Q128资料信息 DMA通道映射 参考示例 打开测试项目 项目配置编写&a…

作者头像 李华
网站建设 2026/5/15 16:27:24

嵌入式分析深度解构:衡石科技如何在客户产品中实现“零代码”BI集成

引言&#xff1a;当数据洞察成为产品的一部分在数字化转型浪潮下&#xff0c;企业客户不再满足于独立的数据分析工具&#xff0c;而是希望数据洞察能力能够无缝嵌入到日常工作流程中。传统BI工具需要用户在多个系统间切换&#xff0c;而嵌入式分析则像一块“智能芯片”&#xf…

作者头像 李华
网站建设 2026/5/13 3:53:09

【课程设计/毕业设计】基于web的饰品商城的设计与实现基于springboot的饰品商城系统【附源码、数据库、万字文档】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华