面试题:什么是 Java 的 Semaphore?

Java 中的 Semaphorejava.util.concurrent 包提供的一个资源访问控制工具类,用于限制同时访问的线程数量,从而实现对共享资源的并发控制。它是基于 AQS(AbstractQueuedSynchronizer) 实现的。


🌟 一、核心概念

1. 信号量(Semaphore)

  • 本质上是一个计数器,表示当前可用资源的数量。
  • 线程在访问资源前需要先获取信号量(acquire()),如果信号量计数大于 0,则允许访问,并将计数减 1。
  • 使用完资源后释放信号量(release()),计数加 1,唤醒等待线程。

2. 公平性(Fairness)

  • 非公平模式(默认):允许线程“插队”尝试获取信号量。
  • 公平模式:线程按照等待顺序获取资源(先进先出)。
// 示例:创建一个公平的信号量,初始许可数为3
Semaphore semaphore = new Semaphore(3, true);

🧠 二、核心方法

方法名描述
acquire()获取一个许可,如果没有可用许可则阻塞。
acquire(int permits)获取指定数量的许可,阻塞直到满足条件。
tryAcquire()尝试获取一个许可,立即返回是否成功(不阻塞)。
tryAcquire(int permits)尝试获取指定数量的许可。
tryAcquire(long timeout, TimeUnit unit)在指定时间内尝试获取许可。
release()释放一个许可。
release(int permits)释放指定数量的许可。
availablePermits()返回当前可用的许可数量。
drainPermits()获取并返回所有可用许可,剩余为 0。

🎯 三、典型使用场景

1. 资源池管理

限制同时访问数据库连接、线程池等资源的数量。

Semaphore pool = new Semaphore(5); // 模拟连接池最多5个连接

pool.acquire(); // 获取连接
// 使用资源...
pool.release(); // 释放连接

2. 限流(Rate Limiting)

控制单位时间内并发请求的数量,防止系统过载。

Semaphore rateLimiter = new Semaphore(10); // 每秒最多处理10个请求

new Thread(() -> {
    rateLimiter.acquire();
    try {
        // 处理请求
    } finally {
        rateLimiter.release();
    }
}).start();

3. 互斥锁(Mutex)

使用许可数为 1 的信号量实现二元互斥锁(类似 ReentrantLock)。

Semaphore mutex = new Semaphore(1);

mutex.acquire();
try {
    // 临界区代码
} finally {
    mutex.release();
}

4. 生产者-消费者模型

控制缓冲区的读写操作,避免资源溢出或空读。


🧪 四、与 CountDownLatch / CyclicBarrier 的区别

特性SemaphoreCountDownLatchCyclicBarrier
用途控制并发访问资源主线程等待多个线程完成多线程互相等待到达屏障点
计数方向可增可减(acquire/release)递减(countDown)递增(await)
可重用性
是否支持超时支持支持支持
是否公平支持设置不适用不适用
是否可重置

🧾 五、示例代码:模拟资源池

import java.util.concurrent.Semaphore;

public class ResourcePool {
    private final Semaphore semaphore = new Semaphore(3); // 同时最多3个线程访问

    public void useResource() {
        try {
            semaphore.acquire();
            System.out.println(Thread.currentThread().getName() + " 正在使用资源...");
            Thread.sleep(2000); // 模拟使用资源
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            semaphore.release();
            System.out.println(Thread.currentThread().getName() + " 已释放资源");
        }
    }

    public static void main(String[] args) {
        ResourcePool pool = new ResourcePool();
        for (int i = 1; i <= 5; i++) {
            new Thread(pool::useResource, "Thread-" + i).start();
        }
    }
}

⚠️ 六、注意事项

  1. acquire() 会响应中断
  • 必须正确处理 InterruptedException,避免线程卡死。
  1. 确保 release() 被调用
  • 使用 try-finally 块保证资源释放,避免死锁。
  1. 许可数不能为负数
  • 初始化时传入负数会抛出 IllegalArgumentException
  1. 公平性影响性能
  • 公平模式虽然避免饥饿,但可能带来性能开销。
  1. 许可数可大于资源数
  • 语义上可以设置许可数大于实际资源数,需开发者自行控制逻辑。

✅ 七、总结

特性说明
作用控制同时访问的线程数量,实现资源同步和限流
底层实现基于 AQS(AbstractQueuedSynchronizer)
核心方法acquire()release()tryAcquire()
典型场景资源池、限流、互斥锁、生产者消费者模型
与其他工具区别与 CountDownLatch 和 CyclicBarrier 各有适用场景
注意事项处理中断、确保释放、合理设置许可数和公平性

THE END
喜欢就支持一下吧
点赞10 分享