Semaphore是Java并发包java.util.concurrent下的一个同步工具类,它通过维护一组许可证来控制对共享资源的访问权限,线程在访问资源前需要先获取许可证,访问完成后释放许可证,当许可证不足时线程会被阻塞等待。这种机制既可以限制同时访问共享资源的线程数量,也能通过许可证的分配逻辑实现线程间的交替执行。

Semaphore核心方法说明
Semaphore的常用构造方法和方法如下:
- Semaphore(int permits):创建指定许可证数量的Semaphore,默认采用非公平模式
- Semaphore(int permits, boolean fair):创建指定许可证数量的Semaphore,fair为true时采用公平模式,线程按等待顺序获取许可证
- acquire():从Semaphore中获取一个许可证,若没有可用许可证则当前线程阻塞
- release():释放一个许可证,将其返回给Semaphore
- availablePermits():返回当前可用的许可证数量
使用Semaphore实现两个线程交替执行
交替执行的核心思路是为两个线程分别分配不同的Semaphore,初始时只给第一个线程的Semaphore发放许可证,第一个线程执行完成后释放第二个线程的Semaphore许可证,同时释放自身的许可证,以此类推实现交替逻辑。
以下示例实现线程A和线程B交替打印数字:
import java.util.concurrent.Semaphore;
public class SemaphoreAlternateDemo {
// 控制线程A的Semaphore,初始发放1个许可证
private static Semaphore semaphoreA = new Semaphore(1);
// 控制线程B的Semaphore,初始不发放许可证
private static Semaphore semaphoreB = new Semaphore(0);
// 打印次数
private static final int PRINT_COUNT = 5;
static class ThreadA extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < PRINT_COUNT; i++) {
// 获取线程A的许可证
semaphoreA.acquire();
System.out.println("线程A打印: " + i);
// 释放线程B的许可证
semaphoreB.release();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
static class ThreadB extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < PRINT_COUNT; i++) {
// 获取线程B的许可证
semaphoreB.acquire();
System.out.println("线程B打印: " + i);
// 释放线程A的许可证
semaphoreA.release();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) {
new ThreadA().start();
new ThreadB().start();
}
}
运行上述代码可以看到线程A和线程B交替输出内容,执行逻辑为:线程A初始有许可证可以执行,执行后释放线程B的许可证,此时线程A的许可证被释放后重新可用,线程B获取许可证执行后释放线程A的许可证,循环往复直到打印次数完成。
使用Semaphore管理共享资源访问
当有多个线程需要访问同一个共享资源,且需要限制同时访问的线程数量时,可以使用Semaphore控制并发数。例如数据库连接池、文件读写等场景,都可以用Semaphore限制同时操作的线程数。
以下示例模拟限制同时访问共享资源的线程数为3:
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class SemaphoreResourceDemo {
// 共享资源,模拟需要限制访问的资源
private static final String SHARED_RESOURCE = "共享资源实例";
// Semaphore,初始3个许可证,最多允许3个线程同时访问
private static Semaphore semaphore = new Semaphore(3);
static class WorkerThread extends Thread {
private String threadName;
public WorkerThread(String threadName) {
this.threadName = threadName;
}
@Override
public void run() {
try {
System.out.println(threadName + " 尝试获取资源访问权限");
// 获取许可证,最多等待2秒
if (semaphore.tryAcquire(2, TimeUnit.SECONDS)) {
try {
System.out.println(threadName + " 成功获取权限,开始访问资源: " + SHARED_RESOURCE);
// 模拟资源访问耗时
TimeUnit.SECONDS.sleep(1);
System.out.println(threadName + " 访问资源完成");
} finally {
// 释放许可证
semaphore.release();
System.out.println(threadName + " 释放资源访问权限,当前可用许可证数: " + semaphore.availablePermits());
}
} else {
System.out.println(threadName + " 获取权限超时,无法访问资源");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) {
// 创建6个线程尝试访问资源
for (int i = 0; i < 6; i++) {
new WorkerThread("线程" + i).start();
}
}
}
运行代码可以看到最多同时有3个线程访问共享资源,其余线程需要等待许可证释放后才能获取访问权限,有效避免了过多线程同时访问资源导致的性能问题或资源冲突。
使用Semaphore的注意事项
- 如果使用tryAcquire方法获取许可证,需要在finally块中确保许可证被释放,避免许可证泄露导致后续线程无法获取权限
- 公平模式虽然可以避免线程饥饿,但会带来额外的性能开销,非必要场景建议使用非公平模式
- Semaphore的许可证数量可以动态变化,release方法可以释放多个许可证,也可以超过初始设置的许可证数量,使用时需要注意逻辑合理性
- 不要在获取许可证后长时间阻塞线程,否则会导致其他等待的线程长时间无法获取权限,影响程序整体性能