一、前言
分析了CountDownLatch原始碼後,下面接著分析Semaphore的原始碼。Semaphore稱為計數訊號量,它允許n個任務同時訪問某個資源,可以將訊號量看做是在向外分發使用資源的許可證,只有成功獲取許可證,才能使用資源。下面開始分析Semaphore的原始碼。
二、Semaphore的資料結構
分析原始碼可以知道,Semaphore底層是基於AbstractQueuedSynchronizer來實現的,所以,Semaphore的資料結構也依託於AQS的資料結構,在前面對AQS的分析中已經指出了其資料結構,在這裡不再累贅。
三、Semaphore原始碼分析
3.1 類的繼承關係
public class Semaphore implements java.io.Serializable {}
說明:Semaphore實現了Serializable介面,即可以進行序列化。
3.2 類的內部類
Semaphore總共有三個內部類,並且三個內部類是緊密相關的,下面先看三個類的關係。
說明:Semaphore與ReentrantLock的內部類的結構相同,類內部總共存在Sync、NonfairSync、FairSync三個類,NonfairSync與FairSync類繼承自Sync類,Sync類繼承自AbstractQueuedSynchronizer抽象類。下面逐個進行分析。
1. Sync類
Sync類的原始碼如下。
// 內部類,繼承自AQS 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)) // 許可小於0或者比較並且設定狀態成功 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; } } // 根據指定的縮減量減小可用許可的數目 final void reducePermits(int reductions) { for (;;) { // 無限迴圈 // 獲取許可 int current = getState(); // 可用的許可 int next = current - reductions; if (next > current) // underflow throw new Error("Permit count underflow"); if (compareAndSetState(current, next)) // 比較並進行設定成功 return; } } // 獲取並返回立即可用的所有許可 final int drainPermits() { for (;;) { // 無限迴圈 // 獲取許可 int current = getState(); if (current == 0 || compareAndSetState(current, 0)) // 許可為0或者比較並設定成功 return current; } } }
說明:Sync類的屬性相對簡單,只有一個版本號,Sync類存在如下方法和作用如下。
2. NonfairSync類
NonfairSync類繼承了Sync類,表示採用非公平策略獲取資源,其只有一個tryAcquireShared方法,重寫了AQS的該方法,其原始碼如下。
static final class NonfairSync extends Sync { // 版本號 private static final long serialVersionUID = -2694183684443567898L; // 建構函式 NonfairSync(int permits) { super(permits); } // 共享模式下獲取 protected int tryAcquireShared(int acquires) { return nonfairTryAcquireShared(acquires); } }
說明:從tryAcquireShared方法的原始碼可知,其會呼叫父類Sync的nonfairTryAcquireShared方法,表示按照非公平策略進行資源的獲取。
3. FairSync類
FairSync類繼承了Sync類,表示採用公平策略獲取資源,其只有一個tryAcquireShared方法,重寫了AQS的該方法,其原始碼如下。
protected int tryAcquireShared(int acquires) { for (;;) { // 無限迴圈 if (hasQueuedPredecessors()) // 同步佇列中存在其他節點 return -1; // 獲取許可 int available = getState(); // 剩餘的許可 int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) // 剩餘的許可小於0或者比較設定成功 return remaining; } }
說明:從tryAcquireShared方法的原始碼可知,它使用公平策略來獲取資源,它會判斷同步佇列中是否存在其他的等待節點。
3.3 類的屬性
public class Semaphore implements java.io.Serializable { // 版本號 private static final long serialVersionUID = -3222578661600680210L; // 屬性 private final Sync sync; }
說明:Semaphore自身只有兩個屬性,最重要的是sync屬性,基於Semaphore物件的操作絕大多數都轉移到了對sync的操作。
3.4 類的建構函式
1. Semaphore(int)型建構函式
public Semaphore(int permits) { sync = new NonfairSync(permits); }
說明:該建構函式會建立具有給定的許可數和非公平的公平設定的Semaphore。
2. Semaphore(int, boolean)型建構函式
public Semaphore(int permits, boolean fair) { sync = fair ? new FairSync(permits) : new NonfairSync(permits); }
說明:該建構函式會建立具有給定的許可數和給定的公平設定的Semaphore。
3.5 核心函式分析
1. acquire函式
此方法從訊號量獲取一個(多個)許可,在提供一個許可前一直將執行緒阻塞,或者執行緒被中斷,其原始碼如下
public void acquire() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
說明:該方法中將會呼叫Sync物件的acquireSharedInterruptibly(從AQS繼承而來的方法)方法,而acquireSharedInterruptibly方法在上一篇CountDownLatch中已經進行了分析,在此不再累贅。
最終可以獲取大致的方法呼叫序列(假設使用非公平策略)。如下圖所示。
說明:上圖只是給出了大體會呼叫到的方法,和具體的示例可能會有些差別,之後會根據具體的示例進行分析。
2. release函式
此方法釋放一個(多個)許可,將其返回給訊號量,原始碼如下。
public void release() { sync.releaseShared(1); }
說明:該方法中將會呼叫Sync物件的releaseShared(從AQS繼承而來的方法)方法,而releaseShared方法在上一篇CountDownLatch中已經進行了分析,在此不再累贅。
最終可以獲取大致的方法呼叫序列(假設使用非公平策略)。如下圖所示。
說明:上圖只是給出了大體會呼叫到的方法,和具體的示例可能會有些差別,之後會根據具體的示例進行分析。
四、示例
下面給出了一個使用Semaphore的示例。
package com.hust.grid.leesf.semaphore; import java.util.concurrent.Semaphore; class MyThread extends Thread { private Semaphore semaphore; public MyThread(String name, Semaphore semaphore) { super(name); this.semaphore = semaphore; } public void run() { int count = 3; System.out.println(Thread.currentThread().getName() + " trying to acquire"); try { semaphore.acquire(count); System.out.println(Thread.currentThread().getName() + " acquire successfully"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(count); System.out.println(Thread.currentThread().getName() + " release successfully"); } } } public class SemaphoreDemo { public final static int SEM_SIZE = 10; public static void main(String[] args) { Semaphore semaphore = new Semaphore(SEM_SIZE); MyThread t1 = new MyThread("t1", semaphore); MyThread t2 = new MyThread("t2", semaphore); t1.start(); t2.start(); int permits = 5; System.out.println(Thread.currentThread().getName() + " trying to acquire"); try { semaphore.acquire(permits); System.out.println(Thread.currentThread().getName() + " acquire successfully"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { semaphore.release(); System.out.println(Thread.currentThread().getName() + " release successfully"); } } }
執行結果(某一次):
main trying to acquire
main acquire successfully
t1 trying to acquire
t1 acquire successfully
t2 trying to acquire
t1 release successfully
main release successfully
t2 acquire successfully
t2 release successfully
說明:首先,生成一個訊號量,訊號量有10個許可,然後,main,t1,t2三個執行緒獲取許可執行,根據結果,可能存在如下的一種時序。
說明:如上圖所示,首先,main執行緒執行acquire操作,並且成功獲得許可,之後t1執行緒執行acquire操作,成功獲得許可,之後t2執行acquire操作,由於此時許可數量不夠,t2執行緒將會阻塞,直到許可可用。之後t1執行緒釋放許可,main執行緒釋放許可,此時的許可數量可以滿足t2執行緒的要求,所以,此時t2執行緒會成功獲得許可執行,t2執行完成後釋放許可。下面進行詳細分析。
① main執行緒執行semaphore.acquire操作。主要的函式呼叫如下圖所示。
說明:此時,可以看到只是AQS的state變為了5,main執行緒並沒有被阻塞,可以繼續執行。
② t1執行緒執行semaphore.acquire操作。主要的函式呼叫如下圖所示。
說明:此時,可以看到只是AQS的state變為了2,t1執行緒並沒有被阻塞,可以繼續執行。
③ t2執行緒執行semaphore.acquire操作。主要的函式呼叫如下圖所示。
說明:此時,t2執行緒獲取許可不會成功,之後會導致其被禁止執行,值得注意的是,AQS的state還是為2。
④ t1執行semaphore.release操作。主要的函式呼叫如下圖所示。
說明:此時,t2執行緒將會被unpark,並且AQS的state為5,t2獲取cpu資源後可以繼續執行。
⑤ main執行緒執行semaphore.release操作。主要的函式呼叫如下圖所示。
說明:此時,t2執行緒還會被unpark,但是不會產生影響,此時,只要t2執行緒獲得CPU資源就可以執行了。此時,AQS的state為10。
⑥ t2獲取CPU資源,繼續執行,此時t2需要恢復現場,回到parkAndCheckInterrupt函式中,也是在should繼續執行。主要的函式呼叫如下圖所示。
說明:此時,可以看到,Sync queue中只有一個結點,頭結點與尾節點都指向該結點,在setHeadAndPropagate的函式中會設定頭結點並且會unpark佇列中的其他結點。
⑦ t2執行緒執行semaphore.release操作。主要的函式呼叫如下圖所示。
說明:t2執行緒經過release後,此時訊號量的許可又變為10個了,此時Sync queue中的結點還是沒有變化。
五、總結
經過分析可知Semaphore的內部工作流程也是基於AQS,並且不同於CyclicBarrier和ReentrantLock,單獨使用Semaphore是不會使用到AQS的條件佇列的,其實,只有進行await操作才會進入條件佇列,其他的都是在同步佇列中,只是當前執行緒會被park。謝謝各位園友的觀看~