在Java中Semaphore如何控制并发访问_Java信号量机制解析

发布时间 - 2026-02-02 00:00:00    点击率:
new Semaphore(1) 不等于 synchronized,因前者是基于AQS的可跨线程释放的共享锁,后者是JVM层必须同栈帧成对使用的独占锁;且Semaphore阻塞时线程状态为WAITING而非BLOCKED。

为什么 new Semaphore(1) 不等于 synchronized

因为 Semaphore 是基于 AQS 的共享锁,而 synchronized 是 JVM 层的独占锁;前者可跨线程、跨方法释放(比如 acquire 在线程 A,release 在线程 B),后者必须成对出现在同一个栈帧里。实际用错时常见现象是:线程卡死在 acquire(),但没任何线程调用 release() —— 尤其在异常路径下忘记 finally 释放。

实操建议:

  • 永远把 acquire() 放在 try 前,release() 放在 finally 块里
  • 避免用 new Semaphore(1) 替代 synchronized,除非你需要超时、中断响应或跨上下文释放
  • Semaphore 构造函数第二个参数 fair 默认为 false,非公平模式下可能造成线程饥饿,高一致性要求场景应显式设为 true

acquire() 阻塞时线程状态到底是 WAITING 还是 BLOCKED

WAITING。因为 Semaphore 底层调用的是 LockSupport.park(),而非进入 monitor 锁竞争队列。这会影响线程 dump 分析:看到 java.lang.Thread.State: WAITING (parking) 就该想到可能是 SemaphoreCountDownLatchCondition 导致的挂起,而不是锁竞争。

关键区别:

  • BLOCKED:在 synchronized 或 ReentrantLock.lock() 等待获取 monitor 或 AQS 同步队列头节点时出现
  • WAITING:调用 acquire()await()join() 等无超时阻塞方法后进入
  • 若用了 acquireUninterruptibly(),即使被中断,线程仍保持 WAITING,不会抛 InterruptedException

如何安全地限制数据库连接池之外的外部资源访问

比如调用第三方 HTTP 接口,每秒最多 10 次请求。这时不能直接用连接池自身的限流(如 HikariCP 的 maximumPoolSize),而要用 Semaphore 包裹调用逻辑。

注意点:

  • 信号量实例必须是共享的单例,不能每次请求都 new 一个
  • 超时控制优先用 tryAcquire(long timeout, TimeUnit unit),避免无限等待拖垮整个服务
  • 如果接口返回 429(Too Many Requests),应主动 release() 并补偿令牌,否则下次请求仍会被拒绝
  • 不要在 acquire() 和实际调用之间插入耗时操作(如日志序列化、参数校验),否则信号量保护的时间窗口变大,失去限流意义

permits 数量设太大或太小会有什么实际影响

设太小(如 new Semaphore(2) 用于处理 100 QPS 的 API)会导致大量线程排队,getQueueLength() 持续增长,CPU 花在 AQS 队列维护上,吞吐反而下降;设太大(如 1000)则失去限流作用,还可能掩盖下游真实容量瓶颈。

更隐蔽的问题:

  • 初始 permits 设为 0 可实现“冷启动开关”——后续用 release(n) 动态放开,但要注意没有线程能 acquire() 成功,直到第一次 release()
  • availablePermits() 返回的是当前剩余数,不是初始值,不能靠它判断是否“刚初始化”
  • 调用 drainPermits() 会清空所有可用令牌并返回数量,适合做瞬时熔断,但之后必须配对 release(n) 才能恢

    复,否则永久不可用

真正难的是根据下游 SLA(比如平均响应 200ms,错误率 getQueueLength() 和 hasQueuedThreads(),而不是拍脑袋定个数字。


# java  #   # ai  # 区别  # 并发访问  # 为什么  # jvm  # 构造函数  # try  # 接口  # finally  # 线程  # Thread  # 并发  # 数据库  # http  # 的是  # 信号量  # 放在  # 令牌  # 设为  # 太大  # 而非  # 太小  # 不等于  # 而不是 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Python自然语言搜索引擎项目教程_倒排索引查询优化案例  Laravel如何配置和使用缓存?(Redis代码示例)  Linux后台任务运行方法_nohup与&使用技巧【技巧】  厦门模型网站设计制作公司,厦门航空飞机模型掉色怎么办?  Laravel事件监听器怎么写_Laravel Event和Listener使用教程  手机网站制作平台,手机靓号代理商怎么制作属于自己的手机靓号网站?  DeepSeek是免费使用的吗 DeepSeek收费模式与Pro版本功能详解  html5如何实现懒加载图片_ intersectionobserver api用法【教程】  Laravel怎么配置不同环境的数据库_Laravel本地测试与生产环境动态切换【方法】  Laravel怎么生成URL_Laravel路由命名与URL生成函数详解  Win11应用商店下载慢怎么办 Win11更改DNS提速下载【修复】  购物网站制作费用多少,开办网上购物网站,需要办理哪些手续?  Laravel怎么清理缓存_Laravel optimize clear命令详解  Laravel如何处理文件上传_Laravel Storage门面实现文件存储与管理  敲碗10年!Mac系列传将迎来「触控与联网」双革新  如何实现建站之星域名转发设置?  C++用Dijkstra(迪杰斯特拉)算法求最短路径  zabbix利用python脚本发送报警邮件的方法  javascript事件捕获机制【深入分析IE和DOM中的事件模型】  图片制作网站免费软件,有没有免费的网站或软件可以将图片批量转为A4大小的pdf?  Win11怎么设置虚拟桌面 Win11新建多桌面切换操作【技巧】  Win11怎样安装网易有道词典_Win11安装词典教程【步骤】  BootStrap整体框架之基础布局组件  laravel怎么在请求结束后执行任务(Terminable Middleware)_laravel Terminable Middleware请求结束任务执行方法  如何用手机制作网站和网页,手机移动端的网站能制作成中英双语的吗?  常州企业网站制作公司,全国继续教育网怎么登录?  Laravel如何实现全文搜索_Laravel Scout集成Algolia或Meilisearch教程  html5如何设置样式_HTML5样式设置方法与CSS应用技巧【教程】  如何为不同团队 ID 动态生成多个非值班状态按钮  浅谈redis在项目中的应用  Windows10如何删除恢复分区_Win10 Diskpart命令强制删除分区  为什么php本地部署后css不生效_静态资源加载失败修复技巧【技巧】  制作网站软件推荐手机版,如何制作属于自己的手机网站app应用?  Android仿QQ列表左滑删除操作  ChatGPT回答中断怎么办 引导AI继续输出完整内容的方法  智能起名网站制作软件有哪些,制作logo的软件?  Laravel怎么发送邮件_Laravel Mail类SMTP配置教程  韩国代理服务器如何选?解析IP设置技巧与跨境访问优化指南  Laravel中间件起什么作用_Laravel Middleware请求生命周期与自定义详解  ,怎么在广州志愿者网站注册?  今日头条微视频如何找选题 今日头条微视频找选题技巧【指南】  Laravel控制器是什么_Laravel MVC架构中Controller的作用与实践  Laravel如何使用Service Container和依赖注入?(代码示例)  Laravel怎么创建控制器Controller_Laravel路由绑定与控制器逻辑编写【指南】  制作ppt免费网站有哪些,有哪些比较好的ppt模板下载网站?  JavaScript如何操作视频_媒体API怎么控制播放  活动邀请函制作网站有哪些,活动邀请函文案?  如何在阿里云完成域名注册与建站?  再谈Python中的字符串与字符编码(推荐)  Laravel的.env文件有什么用_Laravel环境变量配置与管理详解