Semaphore
是 Java 併發包 (java.util.concurrent
) 中的重要工具,主要用於控制多執行緒對共享資源的併發訪問量。它可以設定“許可證”(permit)的數量,並允許指定數量的執行緒同時訪問某一資源,適合限流、資源池等場景。下面從原始碼設計、底層原理、應用場景、以及與其它 JUC 工具的對比來詳細剖析 Semaphore。
一、Semaphore 的基本原理
Semaphore
本質上是一種計數訊號量,內部維護一個許可計數,每個執行緒在進入時需要申請一個許可(acquire),完成後釋放該許可(release)。當許可計數為零時,其他執行緒會阻塞,直到有執行緒釋放許可。
1. 計數和許可
- 許可數:Semaphore 在初始化時設定許可數,通常表示可以同時訪問資源的執行緒數量。
- 計數增減:呼叫
acquire()
時減少許可數,呼叫release()
時增加許可數。 - 公平性:Semaphore 可以設定為“公平”模式,保證執行緒按 FIFO 順序獲得許可。預設是“非公平”模式,效能更高,但執行緒獲取許可的順序不保證。
二、底層實現與原始碼解析
Semaphore 基於 AbstractQueuedSynchronizer
(AQS) 實現,其實現方式和 CountDownLatch
類似,但使用了 AQS 的共享模式,並對許可計數進行精確管理。
1. AQS 的共享模式
Semaphore 使用 AQS 的共享模式(Shared),其中 AQS 的 state
表示剩餘許可數。acquireShared()
方法用於申請許可,releaseShared()
方法用於釋放許可。
2. 內部類 Sync 的實現
Semaphore 的核心實現依賴於其內部類 Sync
,Sync 是 AbstractQueuedSynchronizer
的子類。根據是否是公平模式,有兩種實現:NonfairSync
和 FairSync
。
- NonfairSync:非公平模式。執行緒呼叫
acquire
時直接嘗試獲取許可,不保證順序。 - FairSync:公平模式。執行緒獲取許可遵循佇列順序。
abstract static class Sync extends AbstractQueuedSynchronizer {
Sync(int permits) {
setState(permits); // 設定初始許可數
}
final int getPermits() {
return getState();
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 || compareAndSetState(available, remaining))
return remaining;
}
}
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (compareAndSetState(current, next))
return true;
}
}
}
- 非公平模式(
nonfairTryAcquireShared
):直接嘗試獲取許可,如果成功則更新state
。 - 公平模式(
FairSync
):遵循 AQS 的佇列順序,確保 FIFO 訪問。
三、Semaphore 的使用方法
Semaphore 提供了三種主要方法來操作許可:
- acquire():獲取一個許可,若沒有許可則阻塞。
- release():釋放一個許可,喚醒等待的執行緒。
- tryAcquire():嘗試獲取許可,但不阻塞。
Semaphore semaphore = new Semaphore(3); // 初始許可數為 3
// 獲取許可,若無可用許可則阻塞
semaphore.acquire();
// 釋放許可
semaphore.release();
// 嘗試獲取許可,若不可用立即返回 false,不會阻塞
if (semaphore.tryAcquire()) {
// do something
}
acquire() 原始碼
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
acquireSharedInterruptibly(1)
會呼叫 nonfairTryAcquireShared(1)
或 tryAcquireShared
(公平模式),嘗試獲取許可,若沒有足夠許可則阻塞等待。
release() 原始碼
public void release() {
sync.releaseShared(1);
}
releaseShared(1)
增加許可數並喚醒阻塞執行緒,使等待執行緒得以繼續執行。
四、Semaphore 的應用場景
Semaphore 非常適合控制對有限資源的訪問,典型的應用場景有:
1. 連線池限流
在資料庫連線池中,可以使用 Semaphore 限制同時訪問連線的執行緒數。例如,資料庫連線數有限,透過 Semaphore 控制同時訪問的執行緒數量,避免過度負載。
public class DatabaseConnectionPool {
private static final int MAX_CONNECTIONS = 5;
private final Semaphore semaphore = new Semaphore(MAX_CONNECTIONS);
public Connection getConnection() throws InterruptedException {
semaphore.acquire();
return acquireConnection();
}
public void releaseConnection(Connection conn) {
releaseConnection(conn);
semaphore.release();
}
}
2. 控制執行緒併發數
在高併發任務中,Semaphore 可限制同時執行的執行緒數量,例如,控制下載任務的併發數,避免過多執行緒導致的系統負載過高。
3. 資源分配和限流
Semaphore 適合資源共享場景,如共享印表機、固定執行緒數的執行緒池等,確保資源不會被過度佔用。
五、與其他 JUC 工具的對比
1. CountDownLatch
- 許可機制:Semaphore 的許可數可以動態變化,而 CountDownLatch 的計數器只能遞減。
- 適用場景:Semaphore 控制併發資源訪問數量,而 CountDownLatch 用於執行緒等待。
2. CyclicBarrier
- 作用:Semaphore 適合控制資源訪問併發數,而 CyclicBarrier 則用於執行緒間的同步點,使所有執行緒達到某個屏障時一起繼續。
- 靈活性:Semaphore 更靈活,可隨時
release()
,不必等待所有執行緒。
3. ReentrantLock
- 模式:ReentrantLock 是排他鎖,每次只允許一個執行緒訪問。Semaphore 允許多個執行緒共享資源(多個許可)。
- 適用場景:ReentrantLock 適合獨佔資源場景,Semaphore 適合資源池、限流等。
4. Phaser
- 複雜度:Phaser 用於複雜的多階段同步,可以動態增加/減少執行緒,而 Semaphore 主要用於固定數量資源控制。
- 適用性:Phaser 適合分階段任務同步,而 Semaphore 適合控制資源併發數。
廬山煙雨浙江潮,未至千般恨不消。
到得還來別無事,廬山煙雨浙江潮。
近半年不會再更新了,居家辦公的日子要結束了。讀書,世界就在眼前;不讀書,眼前就是世界。與你共勉!