1. 程式人生 > 其它 >第9講:C# 基本語法 C#運算子 三元運算子(黃菊華NET網站開發、C#網站開發、Razor網站開發教程)

第9講:C# 基本語法 C#運算子 三元運算子(黃菊華NET網站開發、C#網站開發、Razor網站開發教程)

技術標籤:JavaAQS併發程式設計java佇列

目錄:

1 AQS 簡單介紹
2 AQS 原理
(1)AQS 原理概覽
(2) AQS 對資源的共享方式
(3)AQS 底層使用了模板方法模式

內容:

1 AQS 簡單介紹

AQS 的全稱為(AbstractQueuedSynchronizer),這個類在 java.util.concurrent.locks 包下面。
在這裡插入圖片描述
AQS 是一個用來構建鎖和同步器的框架,使用 AQS 能簡單且高效地構造出應用廣泛的大量的同步器,比如我們提到的 ReentrantLock,Semaphore,其他的諸如 ReentrantReadWriteLock,SynchronousQueue,FutureTask(jdk1.7) 等等皆是基於 AQS 的。當然,我們自己也能利用 AQS 非常輕鬆容易地構造出符合我們自己需求的同步器。

2 AQS 原理

在面試中被問到併發知識的時候,大多都會被問到“請你說一下自己對於 AQS原理的理解”。下面給大家一個示例供大家參考,面試不是背題,大家一定要加入自己的思想,即使加入不了自己的思想也要保證自己能夠通俗的講出來而不是背出來。

下面大部分內容其實在 AQS 類註釋上已經給出了,不過是英語看著比較吃力一點,感興趣的話可以看看原始碼。

(1)AQS 原理概覽

AQS 核心思想是,如果被請求的共享資源空閒,則將當前請求資源的執行緒設定為有效的工作執行緒,並且將共享資源設定為鎖定狀態。如果被請求的共享資源被佔用,那麼就需要一套執行緒阻塞等待以及被喚醒時鎖分配的機制,這個機制 AQS 是用 CLH 佇列鎖實現的,即將暫時獲取不到鎖的執行緒加入到佇列中。

CLH(Craig,Landin,and Hagersten)佇列是一個虛擬的雙向佇列(虛擬的雙向佇列即不存在佇列例項,僅存在結點之間的關聯關係)。AQS是將每條請求共享資源的執行緒封裝成一個 CLH 鎖佇列的一個結點(Node)來實現鎖的分配。

看個 AQS(AbstractQueuedSynchronizer)原理圖:
在這裡插入圖片描述

AQS 使用一個 int 成員變數來表示同步狀態,通過內建的 FIFO 佇列來完成獲取資源執行緒的排隊工作。AQS 使用 CAS 對該同步狀態進行原子操作實現對其值的修改。

private volatile int state;//共享變數,使用volatile修飾保證執行緒可見性

狀態資訊通過 protected 型別的 getState , setState , compareAndSetState 進行操作

//返回同步狀態的當前值

protected final int getState() {

    return state;

}

// 設定同步狀態的值

protected final void setState(int newState) {

    state = newState;

}

//原子地(CAS操作)將同步狀態值設定為給定值update如果當前同步狀態的值等於expect(期望值)

protected final boolean compareAndSetState(int expect, int update) {

    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);

}

(2) AQS 對資源的共享方式

AQS 定義兩種資源共享方式
1)Exclusive(獨佔)

只有一個執行緒能執行,如 ReentrantLock。又可分為公平鎖和非公平鎖,ReentrantLock 同時支援兩種鎖,下面以 ReentrantLock 對這兩種鎖的定義做介紹:

公平鎖:按照執行緒在佇列中的排隊順序,先到者先拿到鎖
非公平鎖:當執行緒要獲取鎖時,先通過兩次 CAS 操作去搶鎖,如果沒搶到,當前執行緒再加入到佇列中等待喚醒。

下面來看 ReentrantLock 中相關的原始碼:

ReentrantLock 預設採用非公平鎖,因為考慮獲得更好的效能,通過 boolean 來決定是否用公平鎖(傳入 true 用公平鎖)。

/** Synchronizer providing all implementation mechanics */

private final Sync sync;

public ReentrantLock() {

  // 預設非公平鎖

  sync = new NonfairSync();

}

public ReentrantLock(boolean fair) {

  sync = fair ? new FairSync() : new NonfairSync();

}

ReentrantLock 中公平鎖的 lock 方法

static final class FairSync extends Sync {  final void lock() {

    acquire(1);

 }

  // AbstractQueuedSynchronizer.acquire(int arg)

  public final void acquire(int arg) {

    if (!tryAcquire(arg) &&

      acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

      selfInterrupt();

 }

  protected final boolean tryAcquire(int acquires) {

    final Thread current = Thread.currentThread();

    int c = getState();

    if (c == 0) {

 // 1. 和非公平鎖相比,這裡多了一個判斷:是否有執行緒在等待

      if (!hasQueuedPredecessors() &&

        compareAndSetState(0, acquires)) {

        setExclusiveOwnerThread(current);

        return true;

     }

   }

    else if (current == getExclusiveOwnerThread()) {

      int nextc = c + acquires;

      if (nextc < 0)

        throw new Error("Maximum lock count exceeded");

      setState(nextc);

      return true;

   }

    return false;

 }

}

非公平鎖的 lock 方法:

static final class NonfairSync extends Sync {

  final void lock() {

    // 2. 和公平鎖相比,這裡會直接先進行一次CAS,成功就返回了

    if (compareAndSetState(0, 1))

      setExclusiveOwnerThread(Thread.currentThread());

    else

      acquire(1);

 }

  // AbstractQueuedSynchronizer.acquire(int arg)

  public final void acquire(int arg) {

    if (!tryAcquire(arg) &&

      acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

      selfInterrupt();

 }

  protected final boolean tryAcquire(int acquires) {

    return nonfairTryAcquire(acquires);

 }

}

/**

* Performs non-fair tryLock. tryAcquire is implemented in

* subclasses, but both need nonfair try for trylock method.

*/

final boolean nonfairTryAcquire(int acquires) {

  final Thread current = Thread.currentThread();

  int c = getState();

  if (c == 0) {

    // 這裡沒有對阻塞佇列進行判斷

    if (compareAndSetState(0, acquires)) {

      setExclusiveOwnerThread(current);

      return true;

   }

 }

  else if (current == getExclusiveOwnerThread()) {

    int nextc = c + acquires;

    if (nextc < 0) // overflow

      throw new Error("Maximum lock count exceeded");

    setState(nextc);

 return true;

 }

  return false;

}

總結:公平鎖和非公平鎖只有兩處不同:

  1. 非公平鎖在呼叫 lock 後,首先就會呼叫 CAS 進行一次搶鎖,如果這個時候恰巧鎖沒有被佔用,那麼直接就獲取到鎖返回了。

  2. 非公平鎖在 CAS 失敗後,和公平鎖一樣都會進入到 tryAcquire 方法,在 tryAcquire 方法中,如果發現鎖這個時候被釋放了(state == 0),非公平鎖會直接 CAS 搶鎖,但是公平鎖會判斷等待佇列是否有執行緒處於等待狀態,如果有則不去搶鎖,乖乖排到後面。

公平鎖和非公平鎖就這兩點區別,如果這兩次 CAS 都不成功,那麼後面非公平鎖和公平鎖是一樣的,都要進入到阻塞佇列等待喚醒。

相對來說,非公平鎖會有更好的效能,因為它的吞吐量比較大。當然,非公平鎖讓獲取鎖的時間變得更加不確定,可能會導致在阻塞佇列中的執行緒長期處於飢餓狀態

2)Share(共享)

多個執行緒可同時執行,如 Semaphore/CountDownLatch。Semaphore、CountDownLatCh、CyclicBarrier、ReadWriteLock 我們都會在後面講到。

ReentrantReadWriteLock 可以看成是組合式,因為 ReentrantReadWriteLock 也就是讀寫鎖允許多個執行緒同時對某一資源進行讀。

不同的自定義同步器爭用共享資源的方式也不同。自定義同步器在實現時只需要實現共享資源 state 的獲取與釋放方式即可,至於具體執行緒等待佇列的維護(如獲取資源失敗入隊/喚醒出隊等),AQS 已經在上層已經幫我們實現好了。

(3)AQS 底層使用了模板方法模式

同步器的設計是基於模板方法模式的,如果需要自定義同步器一般的方式是這樣(模板方法模式很經典的一個應用):

  1. 使用者繼承 AbstractQueuedSynchronizer 並重寫指定的方法。(這些重寫方法很簡單,無非是對於共享資源 state 的獲取和釋放)

  2. 將 AQS 組合在自定義同步元件的實現中,並呼叫其模板方法,而這些模板方法會呼叫使用者重寫的方法。

這和我們以往通過實現介面的方式有很大區別,這是模板方法模式很經典的一個運用,下面簡單的給大家介紹一下模板方法模式,模板方法模式是一個很容易理解的設計模式之一。

模板方法模式是基於”繼承“的,主要是為了在不改變模板結構的前提下在子類中重新定義模板中的內容以實現複用程式碼。舉個很簡單的例子假如我們要去一個地方的步驟是:購票buyTicket() ->安檢 securityCheck() ->乘坐某某工具回家 ride() ->到達目的地 arrive() 。我們可能乘坐不同的交通工具回家比如飛機或者火車,所以除了 ride()方法,其他方法的實現幾乎相同。我們可以定義一個包含了這些方法的抽象類,然後使用者根據自己的需要繼承該抽象類然後修改 ride() 方法。

AQS 使用了模板方法模式,自定義同步器時需要重寫下面幾個 AQS 提供的模板方法:

isHeldExclusively()//該執行緒是否正在獨佔資源。只有用到condition才需要去實現它。

tryAcquire(int)//獨佔方式。嘗試獲取資源,成功則返回true,失敗則返回false。

tryRelease(int)//獨佔方式。嘗試釋放資源,成功則返回true,失敗則返回false。

tryAcquireShared(int)//共享方式。嘗試獲取資源。負數表示失敗;0表示成功,但沒有剩餘可用資源;正數表示成功,且有剩餘資源。

tryReleaseShared(int)//共享方式。嘗試釋放資源,成功則返回true,失敗則返回false。

預設情況下,每個方法都丟擲 UnsupportedOperationException 。 這些方法的實現必須是內部執行緒安全的,並且通常應該簡短而不是阻塞。AQS 類中的其他方法都是 final ,所以無法被其他類使用,只有這幾個方法可以被其他類使用。

以 ReentrantLock 為例,state 初始化為 0,表示未鎖定狀態。A 執行緒 lock()時,會呼叫 tryAcquire()獨佔該鎖並將 state+1。此後,其他執行緒再 tryAcquire()時就會失敗,直到 A 執行緒 unlock()到 state=0(即釋放鎖)為止,其它執行緒才有機會獲取該鎖。當然,釋放鎖之前,A 執行緒自己是可以重複獲取此鎖的(state 會累加),這就是可重入的概念。但要注意,獲取多少次就要釋放多麼次,這樣才能保證 state是能回到零態的。

再以 CountDownLatch 以例,任務分為 N 個子執行緒去執行,state 也初始化為 N(注意 N 要與執行緒個數一致)。這 N 個子執行緒是並行執行的,每個子執行緒執行完後 countDown()一次,state 會CAS(Compare and Swap)減 1。等到所有子執行緒都執行完後(即 state=0),會 unpark()主呼叫執行緒,然後主呼叫執行緒就會從 await()函式返回,繼續後餘動作。

一般來說,自定義同步器要麼是獨佔方法,要麼是共享方式,他們也只需實現 tryAcquire-tryRelease 、 tryAcquireShared-tryReleaseShared 中的一種即可。但 AQS 也支援自定義同步器同時實現獨佔和共享兩種方式,如 ReentrantReadWriteLock 。

以上就是本期更新內容,關注下期更精彩哦!