Semaphore (信號量) 維護一組**許可證**,執行緒 acquire() 取得許可、release() 歸還。核心用途:**限流**、資源池連線控制、並發任務閘門。支援公平/非公平模式、可中斷/超時獲取。
核心建構子
new Semaphore(int permits) // 非公平 (預設)
new Semaphore(int permits, boolean fair) // true = 公平 (FIFO)
關鍵 API
| 方法 | 行為 | 拋出 |
acquire() | 阻塞直到取得 1 張許可 | InterruptedException |
acquire(int permits) | 阻塞直到取得 N 張許可 | InterruptedException |
tryAcquire() | 立即嘗試,成功 true/失敗 false | 無 |
tryAcquire(timeout, unit) | 超時等待 | InterruptedException |
release() / release(int) | 歸還許可,喚醒等待執行緒 | 無 |
availablePermits() | 當前可用許可數 (近似值) | 無 |
drainPermits() | 強制取走所有許可,回傳數量 | 無 |
完整範例:模擬 2 個停車位,10 輛車搶車位
import java.util.concurrent.*;
public class SemaphoreParking {
static class Car extends Thread {
private final int id;
private final Semaphore semaphore;
Car(int id, Semaphore semaphore) { this.id = id; this.semaphore = semaphore; }
@Override public void run() {
boolean got = false;
try {
System.out.printf("Car %d 到達,可用車位: %d%n", id, semaphore.availablePermits());
semaphore.acquire(); // 佔車位
got = true;
System.out.printf("Car %d 佔車位%n", id);
Thread.sleep((long) (Math.random() * 3000)); // 停車 0-3 秒
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (got) {
System.out.printf("Car %d 離開%n", id);
semaphore.release(); // 釋放車位
}
}
}
}
public static void main(String[] args) {
Semaphore parking = new Semaphore(2, true); // 2 車位,公平模式
ExecutorService es = Executors.newCachedThreadPool();
for (int i = 1; i <= 10; i++) es.execute(new Car(i, parking));
es.shutdown();
}
}
執行片段
Car 1 到達,可用車位: 2
Car 2 到達,可用車位: 2
Car 1 佔車位
Car 2 佔車位
Car 3 到達,可用車位: 0
Car 4 到達,可用車位: 0
...
Car 1 離開
Car 3 佔車位 ← 公平模式下,等待最久的優先
進階模式
1. 計數器式限流 (每秒最多 N 次)
Semaphore rateLimiter = new Semaphore(100); // 每秒 100 次
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(() -> {
rateLimiter.release(100); // 每秒補滿
// 多餘許可會累積,可用 drainPermits() 重置
}, 1, 1, TimeUnit.SECONDS);
// 業務呼叫
if (rateLimiter.tryAcquire()) { doWork(); } else { reject(); }
2. 階段式啟動同步 (替代 CountDownLatch 可重用)
Semaphore startGate = new Semaphore(0);
// 初始化線程完成後
startGate.release(N); // 釋放 N 張許可,喚醒 N 個工作線程
// 工作線程
startGate.acquire(); // 等待啟動信號
doWork();
Semaphore vs CountDownLatch vs CyclicBarrier
| 特性 | Semaphore | CountDownLatch | CyclicBarrier |
| 核心概念 | 許可證池 | 倒數計數器 | 攔截屏障 |
| 可重用 | ✅ | ❌ | ✅ |
| 雙向同步 | ❌ | ❌ | ✅ (所有方到達才放行) |
| 典型場景 | 資源池、限流 | 啟動等待、一次性聚合 | 多階段並行、迭代同步 |
最佳實務
- 公平模式:高爭用時減少飢餓,但吞吐略低;低爭用用非公平 (預設) 效能更好。
- 釋放許可不需持有:
release() 可由任何執行緒呼叫,不必是 acquire() 的同一執行緒。
- 避免許可洩漏:
acquire() 後務必在 finally 中 release(),否則許可永久流失導致死鎖。
- 監控:
getQueueLength() (等待執行緒數)、hasQueuedThreads() 納入指標監控。