文章目录

Java Semaphore信号量实现限流与资源池控制

发布于 2026-05-15 18:21:35 · 浏览 6 次 · 评论 0 条

Java Semaphore信号量实现限流与资源池控制

Java并发编程中,Semaphore(信号量)是一种核心同步工具,用于控制同时访问特定资源的线程数量。它通过维护一组“许可”,精确限制并发线程数,广泛应用于接口限流、数据库连接池控制等场景。


核心原理解析

Semaphore 的核心逻辑基于“计数器”。线程通过 acquire() 方法获取许可,计数器减一;通过 release() 方法释放许可,计数器加一。若计数器为零,后续请求线程将阻塞,直到有许可释放。

以下是其基本工作流程:

graph TD A["Thread Request"] --> B{"Permits > 0?"} B -- "Yes" --> C["Acquire: Permits--"] C --> D["Execute Task"] D --> E["Release: Permits++"] B -- "No (Block)" --> F["Wait in Queue"] E --> F F --> C

场景一:实现高并发接口限流

当系统需要保护核心接口不被瞬间高并发请求击垮时,可使用 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 方法时,归还资源并增加许可。

  1. 线程 调用 getConnection()
  2. Semaphore 尝试 获取许可。
  3. 若成功,从列表中 移除 一个连接对象并返回。
  4. 若失败(池空),线程 进入 阻塞状态。
  5. 使用完毕后,线程 调用 releaseConnection()
  6. 连接对象 重新加入 列表。
  7. 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(),当前可用许可数将超过构造函数定义的初始值,破坏并发控制逻辑。务必在业务逻辑层面严格匹配获取与释放操作。

评论 (0)

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

扫一扫,手机查看

扫描上方二维码,在手机上查看本文