Semaphore在Druid二次开发中仅作业务层资源准入控制,限制同时使用连接的线程数,不干预连接创建、回收或心跳;必须严格配对acquire与release并置于try-finally中,避免许可泄露。

Java中Semaphore在基于Druid连接池二次开发中,不是用来替代Druid自身并发控制的工具,而是作为**业务层资源准入控制的补充手段**——它不干预连接创建、回收或心跳逻辑,只在特定边界内限制“同时使用连接的线程数”。用对了是轻量兜底,用错了会掩盖真实问题甚至引发连接饥饿。
明确Semaphore的定位:它管什么、不管什么
Semaphore在这里只承担一个职责:对某类**受限的数据库访问行为**做并发数硬限流。比如:
- 某个报表导出接口,最多允许3个线程同时执行长耗时SQL;
- 多个微服务共用同一套Druid实例时,按业务域划分连接配额(A服务5个,B服务3个);
- 脚本类工具中防止突发请求打满DB连接,用信号量兜底保底。
它不负责连接有效性检测、空闲连接驱逐、连接泄漏识别,也不参与Druid内部的锁竞争(如DruidDataSource.getConnection()已用CAS+分段锁优化)。强行把它塞进连接获取主路径,反而会破坏Druid原有高并发设计。
加锁规范:许可获取与归还必须严格配对
所有借用连接的代码路径,必须遵循“acquire → 使用 → release”三步闭环,且release必须出现在finally块中:
立即学习“Java免费学习笔记(深入)”;
- 借连接时优先用
tryAcquire(long timeout, TimeUnit unit),避免无限阻塞; - 归还连接前,先确保Connection已重置(如rollback未提交事务、close Statement);
- release()调用必须与acquire()一一对应,哪怕发生异常也要保证释放;
- 不要在Connection对象上加锁,也不要拿Connection实例作synchronized锁对象。
典型误用场景及规避方式
以下做法在二次开发中常见但危险:
- 在DruidDataSource子类里重写getConnection()并加Semaphore:这绕过了Druid内置连接池管理,导致连接无法被统计、监控和自动回收;
- 用同一个Semaphore管控连接+HTTP客户端+文件句柄,却不做隔离:某模块因文件IO慢拖垮整个信号量,数据库访问也被阻塞;
- acquire后未成功拿到Connection就忘记release:许可泄露,池逐渐不可用,现象类似连接耗尽但JMX里连接数正常。
正确做法是:为每类资源单独声明Semaphore,命名体现用途(如reportExportSemaphore),并在Spring容器中托管其生命周期。
比Semaphore更推荐的替代方案
多数情况下,直接用Semaphore不如借助现有机制:
- Druid本身支持
maxActive(即maxPoolSize),这是连接池层面真正的并发上限,应优先调优该参数; - 业务限流建议走网关层(如Spring Cloud Gateway + Redis RateLimiter)或服务网格(Istio),比代码层信号量更可观测、可动态调整;
- 若需细粒度控制SQL执行频次,可用Druid的
Filter链,在statementExecuteQuery等钩子里嵌入计数逻辑(配合AtomicLong或LongAdder)。
只有当上述方案不可行、且需求明确限定为“固定槽位数”的简单准入控制时,Semaphore才是合理选择。


















