在Java多线程编程场景中,当多个线程需要同时操作同一个共享资源时,很容易出现资源竞争导致的逻辑异常或者数据错误。Semaphore作为JUC包下的同步工具,通过维护一组许可证来限制同时访问共享资源的线程数量,是实现资源访问管控的常用方案。

Semaphore的核心概念
Semaphore的核心逻辑是基于许可证的计数机制,它主要包含以下几个核心属性:
- 许可证数量:初始化时指定的最大可同时访问资源的线程数,线程访问资源前需要先获取许可证,访问完成后释放许可证。
- 公平性策略:构造Semaphore时可以指定是否为公平模式,公平模式下线程获取许可证会按照等待的先后顺序分配,非公平模式则允许线程插队获取许可证,吞吐量更高。
Semaphore的常用方法如下:
| 方法名 | 作用说明 |
|---|---|
| acquire() | 获取一个许可证,如果当前没有可用许可证,线程会进入阻塞状态等待 |
| acquire(int permits) | 获取指定数量的许可证,许可证不足时线程阻塞 |
| tryAcquire() | 尝试获取一个许可证,获取成功返回true,失败返回false,不会阻塞线程 |
| release() | 释放一个许可证,将其返回给Semaphore,唤醒等待的线程 |
| release(int permits) | 释放指定数量的许可证 |
| availablePermits() | 返回当前可用的许可证数量 |
Semaphore控制共享资源的实现示例
下面通过一个模拟停车场停车的场景来演示Semaphore的使用,停车场总共有3个车位,也就是最多同时容纳3辆车停放,超过的车辆需要等待有车位空出后才能进入。
基础使用示例
首先定义停车场资源类,使用Semaphore控制车位访问:
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
// 停车场类,模拟共享资源
class ParkingLot {
// 初始化3个许可证,对应3个车位
private final Semaphore semaphore = new Semaphore(3);
// 车辆进入停车场
public void park(String carName) {
try {
System.out.println(carName + " 尝试进入停车场");
// 获取许可证,没有可用许可证则阻塞等待
semaphore.acquire();
System.out.println(carName + " 成功进入停车场,当前可用车位:" + semaphore.availablePermits());
// 模拟车辆停放时间
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
System.out.println(carName + " 进入停车场被中断");
} finally {
// 释放许可证,无论是否发生异常都要确保许可证被释放
semaphore.release();
System.out.println(carName + " 离开停车场,当前可用车位:" + semaphore.availablePermits());
}
}
}
接下来编写测试类,启动6个线程模拟6辆车同时尝试进入停车场:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SemaphoreDemo {
public static void main(String[] args) {
ParkingLot parkingLot = new ParkingLot();
// 创建固定大小的线程池,模拟6辆车
ExecutorService threadPool = Executors.newFixedThreadPool(6);
for (int i = 1; i <= 6; i++) {
int carId = i;
threadPool.submit(() -> {
parkingLot.park("车辆" + carId);
});
}
// 关闭线程池
threadPool.shutdown();
}
}
运行上述代码后,可以看到同时最多只有3辆车能进入停车场,其余车辆需要等待前面的车辆离开释放车位后才能进入,符合我们对共享资源访问的控制预期。
公平性策略示例
Semaphore默认是非公平模式,我们也可以在构造时指定公平模式,只需要传入第二个参数为true即可:
import java.util.concurrent.Semaphore; // 公平模式的Semaphore,最多2个许可证 Semaphore fairSemaphore = new Semaphore(2, true);
公平模式下,线程会按照请求许可证的顺序依次获取,不会出现插队情况,但是性能会比非公平模式低一些,实际使用时需要根据场景选择。
使用Semaphore的注意事项
- 一定要在finally块中释放许可证,否则如果线程在获取许可证后发生异常没有释放,会导致许可证永远无法回收,最终所有线程都无法获取许可证。
- 如果初始化时许可证数量设置为1,Semaphore的效果和互斥锁类似,但是Semaphore是可以被不同线程释放的,而互斥锁通常要求获取和释放是同一个线程,使用时需要注意这个区别。
- tryAcquire方法适合不需要阻塞等待的场景,比如当资源不足时直接返回提示给用户,而不是让线程一直等待。
- 如果需要获取多个许可证,要确保释放的数量和获取的数量一致,否则会导致许可证数量异常。
适用场景总结
Semaphore适合以下场景:
- 控制同时访问某个特定资源的线程数量,比如数据库连接池的最大连接数控制。
- 限制某个接口的并发请求数量,防止接口被突发流量打垮。
- 实现资源池的访问管控,比如线程池、对象池的可用资源数量限制。
合理使用Semaphore可以有效解决多线程场景下的共享资源竞争问题,提升程序的稳定性和并发处理能力。