Semaphore的核心作用
Semaphore是Java并发包java.util.concurrent下的一个同步工具类,它的核心作用是控制同时访问某个特定资源的线程数量,或者同时执行某个指定操作的线程数量。它内部维护了一个许可证集合,线程在访问资源前需要先获取许可证,访问完成后释放许可证,当许可证不足时,线程会被阻塞等待。

Semaphore的常见使用场景包括:限制数据库连接池的并发访问数量、控制接口的请求并发量、实现停车场车位管理等有限资源管控场景。和synchronized、ReentrantLock这类互斥锁不同,Semaphore允许指定数量的线程同时访问资源,属于共享锁的一种实现。
Semaphore的构造方法
Semaphore提供了两个常用的构造方法,分别用于指定初始许可证数量和是否使用公平策略:
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) {
// 初始化3个许可证,非公平模式
Semaphore semaphore = new Semaphore(3);
// 初始化3个许可证,公平模式,按线程等待顺序分配许可证
Semaphore fairSemaphore = new Semaphore(3, true);
}
}
Semaphore的资源控制模型
Semaphore的底层是基于AbstractQueuedSynchronizer(AQS)实现的,它的资源控制模型可以拆解为三个核心部分:许可证管理、线程阻塞队列、获取释放逻辑。
许可证管理机制
Semaphore的许可证数量本质就是AQS中的state变量,初始化时设置state的值为指定的许可证数量。当线程调用acquire()方法时,会尝试对state执行减1操作,如果减1后state大于等于0,说明获取许可证成功;如果state小于0,说明许可证不足,线程会被加入AQS的等待队列中阻塞。
当线程调用release()方法时,会对state执行加1操作,同时唤醒等待队列中头部的线程,被唤醒的线程会再次尝试获取许可证。
公平与非公平模式差异
Semaphore支持公平和非公平两种模式,两种模式的差异主要体现在许可证分配逻辑上:
- 非公平模式:线程获取许可证时直接尝试修改state,不会检查等待队列中是否有其他线程在等待,可能出现后到的线程先获取许可证的情况,吞吐量更高。
- 公平模式:线程获取许可证前会先检查AQS等待队列中是否有前驱节点,如果有则直接加入队列等待,严格按照线程等待顺序分配许可证,避免线程饥饿,但性能略低。
核心方法说明
Semaphore提供了多个重载的方法用于获取和释放许可证,常用方法如下:
| 方法名 | 作用说明 |
|---|---|
| acquire() | 获取1个许可证,若许可证不足则阻塞当前线程 |
| acquire(int permits) | 获取指定数量的许可证,若许可证不足则阻塞当前线程 |
| tryAcquire() | 尝试获取1个许可证,获取成功返回true,失败立即返回false,不阻塞线程 |
| tryAcquire(long timeout, TimeUnit unit) | 尝试在指定时间内获取1个许可证,超时未获取则返回false |
| release() | 释放1个许可证,唤醒等待队列中的线程 |
| release(int permits) | 释放指定数量的许可证 |
| availablePermits() | 返回当前可用的许可证数量 |
Semaphore使用示例
下面通过一个模拟停车场车位的示例来演示Semaphore的实际使用,假设停车场有3个车位,10辆车依次尝试进入停车场:
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class ParkingLotDemo {
// 停车场有3个车位
private static final Semaphore PARKING_SPOTS = new Semaphore(3);
public static void main(String[] args) {
// 模拟10辆车进入停车场
for (int i = 1; i <= 10; i++) {
int carId = i;
new Thread(() -> {
try {
System.out.println("车辆" + carId + "尝试进入停车场");
// 获取1个车位许可证
PARKING_SPOTS.acquire();
System.out.println("车辆" + carId + "成功进入停车场,当前剩余车位:" + PARKING_SPOTS.availablePermits());
// 模拟停车时间
TimeUnit.SECONDS.sleep(2);
// 释放车位许可证
PARKING_SPOTS.release();
System.out.println("车辆" + carId + "离开停车场,当前剩余车位:" + PARKING_SPOTS.availablePermits());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println("车辆" + carId + "进入停车场被中断");
}
}, "车辆线程-" + i).start();
}
}
}
运行上述代码可以看到,同一时间最多只有3辆车能进入停车场,其余车辆需要等待已有车辆离开释放车位后才能进入,完美实现了有限资源的并发控制。
使用注意事项
在使用Semaphore时需要注意以下几点:
- 获取许可证的数量和释放的数量需要保持一致,如果获取了多个许可证但只释放部分,会导致许可证数量不断减少,最终所有线程都无法获取许可证。
- 如果线程被中断,acquire()方法会抛出InterruptedException,需要根据业务场景合理处理中断逻辑。
- 公平模式虽然能避免线程饥饿,但会增加线程上下文切换的开销,非必要场景下建议使用非公平模式提升性能。
- Semaphore的许可证数量可以动态修改,但是修改后不会影响已经在等待队列中的线程的唤醒顺序。
Semaphore是Java并发编程中非常实用的资源控制工具,合理使用它可以有效避免多线程场景下资源过载的问题,同时也能实现灵活的线程同步逻辑,开发者需要根据实际业务场景选择合适的模式和参数配置。