Java Semaphore信号量实现限流与资源池控制
Java并发编程中,Semaphore(信号量)是一种核心同步工具,用于控制同时访问特定资源的线程数量。它通过维护一组“许可”,精确限制并发线程数,广泛应用于接口限流、数据库连接池控制等场景。
核心原理解析
Semaphore 的核心逻辑基于“计数器”。线程通过 acquire() 方法获取许可,计数器减一;通过 release() 方法释放许可,计数器加一。若计数器为零,后续请求线程将阻塞,直到有许可释放。
以下是其基本工作流程:
场景一:实现高并发接口限流
当系统需要保护核心接口不被瞬间高并发请求击垮时,可使用 Semaphore 实现简单的硬限流。假设某接口最大承受并发量为 5。
1. 初始化信号量
创建 一个 Semaphore 对象,设置许可数量为 5。
// 定义许可数量为5,表示最大允许5个线程同时访问
Semaphore semaphore = new Semaphore(5);
2. 编写业务处理逻辑
在接口处理方法中,使用 try-finally 块确保许可正确释放。
public void accessResource() {
try {
// 获取许可:若无可用许可,线程阻塞等待
semaphore.acquire();
System.out.println("Thread " + Thread.currentThread().getName() + " enters critical section.");
// 模拟业务处理耗时
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放许可:务必在finally块中执行,防止死锁
semaphore.release();
System.out.println("Thread " + Thread.currentThread().getName() + " leaves critical section.");
}
}
3. 测试并发效果
编写 测试代码,启动 10 个线程同时访问。
public static void main(String[] args) {
SemaphoreDemo demo = new SemaphoreDemo();
for (int i = 0; i < 10; i++) {
new Thread(demo::accessResource, "T-" + i).start();
}
}
观察控制台输出,会发现同一时刻仅有 5 条线程打印 "enters" 日志,其余线程处于排队等待状态。
场景二:构建对象资源池
当需要管理昂贵的资源(如数据库连接)时,Semaphore 可用于构建固定大小的资源池,确保连接数不超标。
1. 定义资源池类
声明 一个连接池类,包含连接列表和信号量。
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Semaphore;
public class ConnectionPool {
private final List<Connection> pool = new LinkedList<>();
private final Semaphore semaphore;
// 初始化连接池
public ConnectionPool(int poolSize) {
// 初始化连接对象
for (int i = 0; i < poolSize; i++) {
pool.add(new Connection("Connection-" + i));
}
// 信号量许可数等于连接池大小
this.semaphore = new Semaphore(poolSize);
}
// 获取连接
public Connection getConnection() throws InterruptedException {
semaphore.acquire();
synchronized (pool) {
return pool.remove(0);
}
}
// 归还连接
public void releaseConnection(Connection connection) {
synchronized (pool) {
pool.add(connection);
}
semaphore.release();
}
}
// 简单的连接对象模拟
class Connection {
String name;
public Connection(String name) { this.name = name; }
}
2. 实现资源获取与释放
调用 getConnection 方法时,信号量控制并发数;调用 releaseConnection 方法时,归还资源并增加许可。
- 线程 调用
getConnection()。 - Semaphore 尝试 获取许可。
- 若成功,从列表中 移除 一个连接对象并返回。
- 若失败(池空),线程 进入 阻塞状态。
- 使用完毕后,线程 调用
releaseConnection()。 - 连接对象 重新加入 列表。
- Semaphore 执行
release()唤醒等待线程。
核心方法对比
Semaphore 提供了多种获取许可的方式,适用不同业务场景。
| 方法名 | 行为描述 | 适用场景 |
|---|---|---|
acquire() |
阻塞获取许可,响应中断。 | 标准并发控制,必须拿到资源才执行。 |
acquireUninterruptibly() |
阻塞获取许可,不 响应中断。 | 必须保证获取逻辑完成的场景。 |
tryAcquire() |
尝试获取,立即返回布尔值,不阻塞。 | 限流降级场景,获取不到立即走备用逻辑。 |
tryAcquire(long timeout, TimeUnit unit) |
阻塞指定时间,超时返回 false。 | 允许有限等待的业务场景。 |
1. 应用非阻塞尝试
在限流场景中,若不希望线程长时间排队,使用 tryAcquire() 实现快速失败。
public void tryAccessResource() {
if (semaphore.tryAcquire()) {
try {
// 获取成功,执行业务
System.out.println("Access granted.");
} finally {
semaphore.release();
}
} else {
// 获取失败,执行降级逻辑
System.out.println("Access denied, fallback triggered.");
}
}
注意事项与常见陷阱
1. 许可释放原则
确保 每一个 acquire() 都有对应的 release()。推荐在 try 代码块中获取资源,在 finally 代码块中释放资源。若释放逻辑遗漏,信号量可用许可将逐渐耗尽,最终导致所有线程永久阻塞。
2. 公平性设置
Semaphore 支持公平与非公平模式。初始化 时传入 true 开启公平模式,保证线程按“先入先出”(FIFO)顺序获取许可。
// 公平模式:减少线程饥饿现象
Semaphore fairSemaphore = new Semaphore(5, true);
公平模式虽能保证顺序,但会略微降低吞吐量。默认为非公平模式,吞吐量较高,但可能导致部分线程长时间无法获取许可。
3. 许可数量动态调整
release() 方法可以增加超过初始设置许可的数量。若错误地多次调用 release(),当前可用许可数将超过构造函数定义的初始值,破坏并发控制逻辑。务必在业务逻辑层面严格匹配获取与释放操作。

暂无评论,快来抢沙发吧!