多線程分析之Semaphore

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的強大一時半會說不清楚,希望大家多看看源碼,結合百家之所長,來提升自己。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章