在实际工作中,Semaphore(信号量)的核心价值是 “控制并发访问的资源数量”,尤其适用于 “资源有限但需要多线程共享” 的业务场景。以下是高频且典型的应用场景,结合具体业务逻辑说明:
1. 限流场景:控制接口 / 服务的并发请求量
业务背景
- 后端接口(如查询商品、提交订单)若同时接收过多请求,会导致数据库压力骤增、服务器 CPU / 内存过载,甚至服务宕机。
- 需限制接口的并发请求数(而非 “每秒总请求数 QPS”,QPS 通常用计数器限流,如 Guava RateLimiter;Semaphore 更侧重 “同时处理的请求数”)。
应用示例
- 假设订单提交接口的数据库连接池仅支持 10 个并发写入,可通过 Semaphore 限制该接口的并发请求数为 10:
* 当第 11 个请求到达时,会阻塞等待,直到前 10 个请求中有一个完成并释放 “许可”。
* 避免因并发过高导致数据库连接耗尽、事务超时。
代码核心逻辑(简化)
// 初始化信号量:10个许可(对应10个并发请求)
private static final Semaphore ORDER\_SEMAPHORE = new Semaphore(10);
// 订单提交接口
@PostMapping("/submit-order")
public Result submitOrder(@RequestBody OrderDTO order) throws InterruptedException {
// 1. 尝试获取许可(最多等待3秒,避免请求一直阻塞)
if (!ORDER\_SEMAPHORE.tryAcquire(3, TimeUnit.SECONDS)) {
return Result.fail("当前下单人数过多,请稍后再试");
}
try {
// 2. 执行业务逻辑:校验库存、创建订单、扣减库存、记录日志
orderService.createOrder(order);
return Result.success("下单成功");
} finally {
// 3. 无论业务成功/失败,必须释放许可(避免资源泄漏)
ORDER\_SEMAPHORE.release();
}
}
2. 资源池场景:管理有限的共享资源
业务背景
- 系统中存在 “创建成本高、数量有限” 的资源,如:数据库连接池、Redis 连接池、MQ 生产者 / 消费者连接、第三方 API 调用配额(如调用微信支付 API 的并发数限制)。
- 需确保资源被 “按需获取、用完归还”,避免多个线程争抢同一资源导致异常。
典型案例:数据库连接池(简化原理)
- 传统 JDBC 连接池(如 C3P0、Druid)的底层并发控制,本质上可通过 Semaphore 实现:
* 初始化连接池时,创建 10 个数据库连接(资源),同时初始化一个含 10 个许可的 Semaphore。
* 线程需要执行 SQL 时,先获取 Semaphore 许可,再从连接池拿一个连接;执行完后,归还连接到池,同时释放 Semaphore 许可。
* 若连接池无空闲连接(Semaphore 无许可),线程会阻塞等待,直到有连接归还。
代码核心逻辑(简化)
public class SimpleConnectionPool {
// 1. 初始化10个数据库连接(模拟资源)
private List\<Connection> connections = new ArrayList<>();
// 2. 信号量:控制并发获取连接的数量
private Semaphore semaphore = new Semaphore(10);
// 初始化连接池
public SimpleConnectionPool() {
for (int i = 0; i < 10; i++) {
connections.add(createMockConnection()); // 模拟创建JDBC连接
}
}
// 获取连接
public Connection getConnection() throws InterruptedException {
semaphore.acquire(); // 先拿许可,再拿连接
synchronized (connections) {
return connections.remove(0); // 从池里取一个连接
}
}
// 归还连接
public void releaseConnection(Connection conn) {
synchronized (connections) {
connections.add(conn); // 连接归还给池
}
semaphore.release(); // 释放许可,让其他线程可用
}
// 模拟创建JDBC连接
private Connection createMockConnection() {
return new MockConnection(); // 自定义Mock类,模拟JDBC Connection
}
}
3. 批量任务控制:限制并发执行的任务数
业务背景
- 处理大量批量任务时(如 Excel 导入数据、批量推送消息、定时同步数据),若一次性启动所有任务线程,会导致 CPU / 内存占用过高,甚至触发系统保护机制。
- 需限制 “同时执行的任务数”,避免资源过载。
典型案例:Excel 批量导入用户
- 业务需求:用户上传一个含 1000 条数据的 Excel,需将每条数据解析后插入数据库。
- 问题:若直接创建 1000 个线程执行插入,会导致数据库连接耗尽、SQL 执行排队超时。
- 解决方案:用 Semaphore 限制并发插入的线程数为 20(根据数据库性能调整),让 20 个线程分批处理 1000 条数据。
代码核心逻辑(简化)
public class ExcelImportService {
// 限制并发插入的线程数为20
private static final Semaphore BATCH\_SEMAPHORE = new Semaphore(20);
private ExecutorService executor = Executors.newFixedThreadPool(20); // 线程池配合使用
// 批量导入用户
public void importUsers(List\<UserDTO> userList) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(userList.size()); // 等待所有任务完成
for (UserDTO user : userList) {
BATCH\_SEMAPHORE.acquire(); // 控制并发任务数
executor.submit(() -> {
try {
userMapper.insert(user); // 插入数据库
} finally {
latch.countDown();
BATCH\_SEMAPHORE.release(); // 任务完成,释放许可
}
});
}
latch.await(); // 等待所有用户插入完成
System.out.println("批量导入完成");
}
}
4. 分布式场景:跨服务的并发控制(结合分布式锁)
业务背景
- 单体应用中,Semaphore 仅能控制当前服务的并发;若多个服务实例(如微服务集群)共享同一资源(如一个 Redis 缓存的热点数据、一个物理文件),需跨服务限制并发访问数。
- 此时需结合 “分布式 Semaphore”(如基于 Redis 的 Redisson Semaphore、ZooKeeper 的分布式信号量),实现跨实例的资源并发控制。
典型案例:共享文件上传(避免文件被同时写入)
- 业务需求:多个服务实例(如 3 个文件服务)需要向同一台 FTP 服务器上传文件,FTP 服务器仅支持 5 个并发写入连接。
- 解决方案:用 Redisson 的分布式 Semaphore,初始化 5 个许可,所有服务实例获取许可后才能上传,确保并发不超过 5。
代码核心逻辑(Redisson 示例)
@Service
public class FtpUploadService {
@Autowired
private RedissonClient redissonClient;
// 分布式信号量:控制FTP并发上传数为5
private static final String FTP\_SEMAPHORE\_KEY = "ftp\_upload\_semaphore";
public void uploadFileToFtp(File file) throws InterruptedException {
// 1. 获取分布式信号量(不存在则创建,初始5个许可)
RSemaphore semaphore = redissonClient.getSemaphore(FTP\_SEMAPHORE\_KEY);
semaphore.trySetPermits(5); // 确保许可数为5
// 2. 尝试获取许可(最多等待10秒)
if (!semaphore.tryAcquire(10, TimeUnit.SECONDS)) {
throw new BusinessException("FTP服务器繁忙,请稍后再试");
}
try {
// 3. 执行FTP上传逻辑
ftpClient.upload(file);
} finally {
// 4. 释放许可(跨服务场景必须确保释放,避免死锁)
semaphore.release();
}
}
}
总结:Semaphore 的适用场景核心特征
- 资源有限:共享资源的数量是固定的(如 10 个数据库连接、5 个 FTP 连接)。
- 控制并发:需要限制 “同时访问资源的线程 / 请求数”,而非 “总请求数”。
- 需归还资源:资源使用后必须释放(对应 Semaphore 的release()操作),避免许可泄漏导致资源不可用。
与其他限流 / 并发工具的区别:
- Semaphore:控制 “并发数”(同时处理的数量),适合资源有限场景。
- Guava RateLimiter:控制 “QPS”(每秒总请求数),适合接口限流(如每秒最多 100 次请求)。
- CountDownLatch:控制 “线程等待”(如等待所有任务完成),无资源控制能力。