醋醋百科网

Good Luck To You!

'信号量Semaphore'--相信你会用到

在实际工作中,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 的适用场景核心特征

  1. 资源有限:共享资源的数量是固定的(如 10 个数据库连接、5 个 FTP 连接)。
  2. 控制并发:需要限制 “同时访问资源的线程 / 请求数”,而非 “总请求数”。
  3. 需归还资源:资源使用后必须释放(对应 Semaphore 的release()操作),避免许可泄漏导致资源不可用。

与其他限流 / 并发工具的区别:

  • Semaphore:控制 “并发数”(同时处理的数量),适合资源有限场景。
  • Guava RateLimiter:控制 “QPS”(每秒总请求数),适合接口限流(如每秒最多 100 次请求)。
  • CountDownLatch:控制 “线程等待”(如等待所有任务完成),无资源控制能力。
控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言