在 Java 并发编程中,Semaphore 是一个非常有用的同步工具类,属于 java.util.concurrent 包的一部分。它可以用来控制对某一资源的访问数量,允许多个线程同时访问一定数量的资源。本文将详细介绍 Semaphore 的概念、使用技巧和示例。

1. 什么是 Semaphore?
Semaphore(信号量)是一种用于限制线程并发数量的机制。它维护一个可用的许可数量,每个线程在进入临界区前必须先获得许可,离开时释放许可。信号量可以用来实现各种并发控制,如互斥锁、限流器、生产者-消费者模型等。
Semaphore 主要有两个构造方法:
Semaphore(int permits):创建一个具有指定许可数量的信号量。Semaphore(int permits, boolean fair):指定是否为公平模式的信号量。如果为true,则按照先来先得的顺序分配许可。
2. Semaphore 的核心方法
Semaphore 主要提供以下几个方法来管理许可:
acquire():获取一个许可,如果没有可用的许可,当前线程会被阻塞,直到有可用的许可。acquire(int permits):获取指定数量的许可。tryAcquire():尝试获取一个许可,如果成功则立即返回true,否则返回false。tryAcquire(int permits):尝试获取指定数量的许可。release():释放一个许可。release(int permits):释放指定数量的许可。
3. Semaphore 的使用技巧
Semaphore 可以用于解决以下几种典型的并发控制问题:
1. 控制访问数量
Semaphore 可以限制同时访问某一资源的线程数量。常见场景如:数据库连接池、限流、实现限速等。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private static final int THREAD_COUNT = 30;
private static final Semaphore semaphore = new Semaphore(10); // 最多允许 10 个线程同时访问
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);
for (int i = 0; i < THREAD_COUNT; i++) {
executorService.execute(() -> {
try {
semaphore.acquire(); // 获取一个许可
System.out.println(Thread.currentThread().getName() + " is accessing...");
Thread.sleep(1000); // 模拟访问资源
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // 释放一个许可
}
});
}
executorService.shutdown();
}
}在上述示例中,semaphore 限制了最多有 10 个线程可以同时访问资源。其他线程必须等待,直到有可用的许可。
2. 实现生产者-消费者模式
Semaphore 可以用来实现生产者-消费者模型,其中一个信号量控制生产者生产的产品数量,另一个信号量控制消费者的消费数量。
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.Semaphore;
public class ProducerConsumerExample {
private static final int BUFFER_SIZE = 5;
private static final Semaphore items = new Semaphore(0); // 表示当前有多少个产品可消费
private static final Semaphore spaces = new Semaphore(BUFFER_SIZE); // 表示缓冲区有多少个空间可放入产品
private static final Queue<Integer> buffer = new LinkedList<>();
public static void main(String[] args) {
Thread producer = new Thread(() -> {
try {
int value = 0;
while (true) {
spaces.acquire(); // 等待缓冲区有空位
synchronized (buffer) {
buffer.add(value);
System.out.println("Produced: " + value);
}
items.release(); // 增加一个可消费的产品数量
value++;
Thread.sleep(500); // 模拟生产时间
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread consumer = new Thread(() -> {
try {
while (true) {
items.acquire(); // 等待有产品可消费
int value;
synchronized (buffer) {
value = buffer.poll();
System.out.println("Consumed: " + value);
}
spaces.release(); // 增加一个缓冲区空间
Thread.sleep(1000); // 模拟消费时间
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producer.start();
consumer.start();
}
}在这个例子中,items 信号量表示可以消费的产品数量,而 spaces 信号量表示缓冲区中可用的空间。生产者生产一个产品后,items 许可数增加,spaces 减少;消费者消费一个产品后,items 减少,spaces 增加。
3. 实现互斥锁
Semaphore 的初始许可数为 1 时,可以用来实现互斥锁(类似于 ReentrantLock 或 synchronized)。
import java.util.concurrent.Semaphore;
public class MutexExample {
private static final Semaphore mutex = new Semaphore(1); // 创建一个信号量作为互斥锁
public static void main(String[] args) {
Thread t1 = new Thread(new Task(), "Thread-1");
Thread t2 = new Thread(new Task(), "Thread-2");
t1.start();
t2.start();
}
static class Task implements Runnable {
@Override
public void run() {
try {
mutex.acquire(); // 获取许可,相当于锁定
System.out.println(Thread.currentThread().getName() + " is in critical section.");
Thread.sleep(2000); // 模拟临界区操作
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " is leaving critical section.");
mutex.release(); // 释放许可,相当于解锁
}
}
}
}此示例使用信号量 mutex 实现了一个简单的互斥锁效果,确保同一时间只有一个线程能进入临界区。
4. 总结
Semaphore是一种非常灵活的并发控制工具,可以控制对资源的访问数量。Semaphore常用于实现限流、互斥锁、生产者-消费者模型等并发场景。- 使用
Semaphore可以显式地控制线程的同步行为,但需要小心避免死锁和资源泄露问题。
通过理解 Semaphore 的用法和应用场景,可以编写出更高效和健壮的多线程 Java 程序。










