前言
本篇開始分析Semaphore(信號量)的源碼,分析結束後,會用一個示例展示Semaphore的應用場景。
1、簡介
Semaphore是一個計數信號量,維護了一個信號量許可集。每次調用acquire()都將消耗一個許可,每次調用release()都將歸還一個許可。
2、結構圖
Semaphore的內部維護了一個Sync內部類,Sync是繼承AQS的抽象類,Sync包括兩個子類:"公平信號量"FairSync 和 "非公平信號量"NonfairSync。
3、分析源碼
3.1、信號量構造函數
public Semaphore(int permits) {
//默認構造非公平信號量
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
3.2、公平信號量獲取
Semaphore中的方法源碼:
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
sync中調用的acquireSharedInterruptibly方法在AQS中,源碼如下:
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 如果線程是中斷狀態,則拋出異常。
if (Thread.interrupted())
throw new InterruptedException();
// 嘗試獲取“共享鎖”;獲取成功則直接返回,獲取失敗,則通過doAcquireSharedInterruptibly()獲取
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
tryAcquireShared方法調用的是FairSync中的方法,源碼如下:
protected int tryAcquireShared(int acquires) {
for (;;) {
// 判斷當前隊列前面還有沒有等待的線程
// 若有的話,則返回-1。
if (hasQueuedPredecessors())
return -1;
// 設置“可以獲得的信號量的許可數”
int available = getState();
// 設置剩餘的信號量許可數
int remaining = available - acquires;
// 如果“剩餘的信號量許可數>=0”,則設置“可以獲得的信號量許可數”爲remaining。
//如果“剩餘的信號量許可數小於0”,則進入doAcquireSharedInterruptibly方法
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
下面看看AQS中doAcquireSharedInterruptibly()的源碼實現,此方法在ReentrantReadWriteLock中的doAcquireShared也分析過,再看看:
private void doAcquireSharedInterruptibly(long arg)
throws InterruptedException {
// 創建”當前線程“的Node節點,且Node中記錄的鎖是”共享鎖“類型;並將該節點添加到CLH隊列末尾。
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
// 當前節點的前一個節點。
final Node p = node.predecessor();
// 如果前一個節點是頭節點(說明是第一個排隊的節點)
if (p == head) {
// 再次嘗試獲取讀鎖
long r = tryAcquireShared(arg);
// 如果成功了
if (r >= 0) {
//頭節點後移,並連續傳遞後面所有節點
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// 當前線程一直等待,直到獲取到共享鎖。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
3.3、公平信號量釋放
Semaphore中的方法源碼:
public void release() {
sync.releaseShared(1);
}
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
AQS中的releaseShared方法,源碼如下:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
Sync重寫了tryReleaseShared(),它的源碼如下:
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");
// 如果原子更新state的值成功,就說明釋放許可成功,則返回true
if (compareAndSetState(current, next))
return true;
}
}
如果tryReleaseShared()嘗試釋放共享鎖失敗,則會調用doReleaseShared()去釋放共享鎖。doReleaseShared()的源碼如下:
//此方法只會喚醒一個節點
//和ReentrantReadWriteLock中代碼一樣,不再解釋
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
3.4、非公平信號量獲取和釋放
公平信號量和非公平信號量的釋放是一樣的,下面是非公平信號量的獲取源碼:
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
//這裏和公平信號量相比,少了一個判斷
//少了的判斷是:判斷當前隊列前面還有沒有等待的線程
//這裏就體現了公平和非公平的區別
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
4、應用場景
通過上面的分析,我們知道了Semaphore的內部維護了一個字段state,用來限制同一時間對共同資源的訪問次數,是不是非常像限流,現在我們就模擬限流場景,假設有1000個請求同時進來,我們只處理100個請求,防止服務器壓力過大,代碼如下:
public class SemaphoreLimit {
public static final Semaphore SEMAPHORE = new Semaphore(100);
private static final AtomicInteger SUCCESS = new AtomicInteger();
public static void main(String[] args) {
for (int i=0; i<1000; i++){
new Thread(() -> { method();}).start();
}
System.out.printf("success request number=%d \n",SUCCESS.get());
}
public static void method(){
if(!SEMAPHORE.tryAcquire()){
return;
}
//處理業務
try {
Thread.sleep(100);
SUCCESS.incrementAndGet();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
SEMAPHORE.release();
}
}
}
結束語
本篇分析了Semaphore源碼,瞭解了Semaphore限流的應用場景,下一篇將分析CountDownLatch。
如果本篇的內容對你有幫助,請點個贊再走,謝謝大家!