Semaphore分析由來
網上看了許多講解Semaphore的,用Semaphore來實現順序打印字母,但是可能大家都沒有清楚具體的原因,所以來給大家分析下爲什麼可以使用Semaphore來實現順序打印字母順序。
Semaphore源碼分析
先打開JDK8源碼中的Semaphore,可以看到Semaphore是通過繼承AQS來現實功能(AQS,Doug Lea大神重寫併發包的核心,這個默認自己看過哈,其實蠻簡單,核心原理:通過模板方法,完成流程調用,讓子類實現具體方法,然後實現不同功能)。
說正事,我們貼出一張Semaphore的層級關係圖。
在Semaphore中主要是Sync類實現AbstractQueuedSynchronizer,然後Sync又有兩個實現類,分別是FaireSync和NonfairSync,即公平鎖和非公平鎖。我們來看下Sync中的部分源碼:
/** * Synchronization implementation for semaphore. Uses AQS state * to represent permits. Subclassed into fair and nonfair * versions. (使用AQS的state變量來代表許可,註釋這段還是蠻重要) */ 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 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; } } }
在類中,沒有特別難的方法,都是都過CAS來進行操作,用AQS中的state來當作許可。好了, 有了這一部分基礎,我們可以去看看大家是如何使用Semaphore來實現順序打印的。還是先爲大家貼上代碼:
/** * <br>使用信號量順序打印</br> * * @author lifacheng * @version 1.0 * @date 2019/6/18 11:08 * @since 1.0 */ public class Thread11 { private static Semaphore semaphore1 = new Semaphore(0); private static Semaphore semaphore2 = new Semaphore(1); Thread t1, t2; int count = 10; public Thread11() { t1 = new Thread() { public void run() { try { int i = 0; while(i++ < count) { //獲得許可 semaphore2.acquire(); System.out.print("A"); //初始化許可爲0,處於阻塞,當release獲取許可、 semaphore1.release(); } } catch (Exception e) { e.printStackTrace(); } } }; t2 = new Thread() { public void run() { try { int i = 0; while(i++ < count) { semaphore1.acquire(); System.out.print("B"); semaphore2.release(); } } catch (InterruptedException e) { e.printStackTrace(); } } }; } public void run() { t1.start(); t2.start(); } public static void main(String args[]) throws Exception { Thread11 t = new Thread11(); t.run(); } }
在代碼一開始,初始化了兩個信號量,分別爲:semaphore1,semaphore2,semaphore1的許可書爲0個,semaphore2的許可書爲1個。
acquire()源碼分析
在線程t1的run()方法中,semaphore2執行了acquire()方法,我們打開源碼看看這段邏輯是什麼,
代碼調用了sync中的acquireSharedInterruptibly()方法,此時我們要注意,傳入的參數是1個(這個很重要)。我們接着往下找,找到這個是AQS中實現的方法。
我們找到具體的實現類中的方法,即tryAcquireShare(arg)這個在Semaphore中實現的方法。(在tryAcquireShare(arg)<0時,會進去doAcquireSharedInterruptibly()方法中,當獲取小於0會處於阻塞狀態)
)
我們可以看到有Semaphore有兩個實現類,分別是公平和非公平兩種實現,我們點進去看看到底有何不同。
公平:
非公平:
我們看到,公平就比非公平多了一個判斷,這個判斷就是判斷是否是頭節點(就不點進去看了哈)。
至此,我們可以看到,acquire()方法具體實現就是獲取了一個許可。我們接着看代碼,然後輸出字符A,semaphore1執行了release()方法。
release()方法分析
我們點進release方法查看,
按照和acquire()的相同的邏輯,最後找到如上這段代碼,即在執行release()方法時,會增加1個許可。
代碼邏輯
自此我們知道代碼的邏輯了,來具體分析下代碼。
當線程t1和t2同時開始運行,這個t1開始執行獲取到許可,然後輸出A,這是就算t2拿到CPU執行權,由於初始化許可數爲0,這時acquire()方法獲取不到許可,處於阻塞狀態。只有當t1中的semaphore1執行了release()方法時,纔會增加一個許可,t2獲取到CPU執行權後纔會執行。假設此時t1又獲取到CPU執行權,但是由於只有一個許可,開始獲取過許可,再此獲取會失敗,也會處於阻塞狀態,只有t2線程中semaphore2執行了release()方法纔會增加一個許可,然後t1纔會再次獲取成功並執行。
代碼充分使用了許可數量來控制線程的執行,當線程執行時,相互喚醒,增加許可數量。有點像wait和notify的概念,但是更高級,我們可以增加semaphore來控制多個線程執行順序。 這下次就分析清楚了,爲什麼初始化0個許可數的semaphore仍然可以用來控制。可以說相當神奇。我們也對AQS的強大有了一個小小的瞭解。
總結
AQS的強大一時半會說不清楚,希望大家多看看源碼,結合百家之所長,來提升自己。