目錄
1 前言
本人使用jdk8版本。
Semaphore
(信號量)是用來控制同時訪問特定資源的線程數量,它通過協調各個線程,以保證合理的使用公共資源。
舉個例子,比如××馬路要限制流量,只允許同時有一百輛車在這條路上行使,其他的都必須在路口等待,所以前一百輛車會看到綠燈,可以開進這條馬路,後面的車會看到紅燈,不能駛入××馬路,但是如果前一百輛中有5輛車已經離開了××馬路,那麼後面就允許有5輛車駛入馬路,這個例子裏說的車就是線程,駛入馬路就表示線程在執行,離開馬路就表示線程執行完成,看見紅燈就表示線程被阻塞,不能執行。
2 應用場景
Semaphore
可以用於做流量控制,特別是公用資源有限的應用場景,比如數據庫連接。
假如有一個需求,要從本地磁盤讀取幾萬個文件的數據,因爲都是IO密集型任務,我們可以啓動幾十個線程併發地讀取,但是如果讀到內存後,還需要存儲到數據庫中(必須通過數據庫連接來寫),而數據庫的連接數只有10個,這時我們必須控制只有10個線程同時獲取數據庫連接保存數據,否則會報錯無法獲取數據庫連接。
public class SemaphoreTest {
private static final int THREAD_COUNT = 30;
private static ExecutorService threadPool = Executors.newFixedThreadPool(THREAD_COUNT);
private static Semaphore s = new Semaphore(10);
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
threadPool.execute(new MyRunnable());
}
threadPool.shutdown();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
try {
s.acquire();
System.out.println("save data -- ";
s.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在代碼中,雖然有30
個線程在執行,但是隻允許10
個併發執行。Semaphore
的構造方法Semaphore(int permits)
接受一個整型的數字,表示可用的許可證數量。Semaphore(10)
表示允許10
個線程獲取許可證,也就是最大併發數是10
。
Semaphore
的用法也很簡單,首先線程使用Semaphore
的acquire()
方法獲取一個許可證,使用完之後調用release()
方法歸還許可證。還可以用tryAcquire()
方法嘗試獲取許可證。
3 其它方法
int availablePermits()
:返回此信號量中當前可用的許可證數。int getQueueLength()
:返回正在等待獲取許可證的線程數。boolean hasQueuedThreads()
:是否有線程正在等待獲取許可證。void reducePermits(int reduction)
:減少reduction
個許可證,是個protected
方法。Collection<Thread> getQueuedThreads()
:返回所有等待獲取許可證的線程集合,是個protected
方法。
4 實現原理
Semaphore內部實現了一個AbstractQueuedSynchronizer的子類,並重寫它的tryAcquireShared()、tryReleaseShared()等方法來實現“控制同時訪問特定資源的線程數量”的功能。顯然,實現基於同步器的共享鎖,在構造Semaphore時傳入的int參數就是用來指定共享鎖可以被獲取的最大數,超過最大數後再獲取線程會阻塞,同時一旦一個線程釋放自己的共享鎖,阻塞隊列中所有的等待線程會被喚醒來競爭鎖。多個線程怎麼來競爭鎖呢?Semaphore裏也引入同步器裏的公平鎖和非公平鎖的機制,在構造Semaphore時也可以指定。
4.1 實現的同步器
公平鎖與非公平鎖只在獲取鎖時有區別,釋放時用的是統一方法。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 1192457210091910933L;
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 int tryAcquireShared(int acquires) { // 嘗試獲取公平鎖
for (;;) {
// 判斷是否有前驅節點,是就表情有線程比它更早請求鎖,請求失敗
if (hasQueuedPredecessors())
return -1;
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 (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
}
4.2 執行過程
通過上面的數據庫例子來介紹,構造Semaphore時將鎖的狀態設爲10,然後同時開啓30個線程往數據庫中寫,所有線程都會先調用Semaphore.acquire(),前10個線程會成功獲取鎖,每當獲取成功時鎖的狀態會-1,進而執行保存數據的操作,最終鎖的狀態爲0。當而另外20個線程再獲取鎖會失敗進而阻塞,若此前10個線程中有線程保存完數據釋放鎖了,鎖的狀態+1,會喚醒這個20個線程去競爭鎖(公平與非公平)。