初步瞭解AQS是什麼(二)

JieMingLi發表於2019-04-28

初步瞭解AQS是什麼(二)

前題

在閱讀本文之前,建議先閱讀我的《初步瞭解AQS是什麼(一)》,畢竟有一些內容是和前文是相通的,如果對AQS熟悉的話,也可以直接往下看,不過還是先建議先看下

公平鎖和非公平鎖

ReentrantLock預設使用的是非公平鎖,除非在構造方法裡面傳入true就可以變為公平鎖啦!

 public ReentrantLock() {
        sync = new NonfairSync();
 }

 public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
 }
複製程式碼

先看看公平鎖的lock

 final void lock() {
            acquire(1);
        }
//----------------------------acquire

 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }


//-----------------------------tryAcquire
protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                //因為是公平鎖,那麼到這裡如果有執行緒在等待,就公平而言,肯定是不能搶前面的鎖啦!
                //和非公平鎖的區別
                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

 final void lock() {
            if (compareAndSetState(0, 1)) // 非公平鎖會先試著去佔有這個鎖先,如果不行就再獲取!
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

//-------------------------acquire
 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

//-----------------------tryAcquire
protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }

//-----------------------nonfairTryAcquire
  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的時候,在tryAcquire函式裡面會CAS獲取state,如果恰好成功,那麼就直接取得鎖啦,不用進行下面的操作了

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

  3. 非公平鎖如果兩次CAS都不成功,那麼接下來的操作和公平鎖一樣啦!都要進入到阻塞佇列等待喚醒。

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

生產者消費者模式

在讀下面的內容的時候,讓我們來先了解一下生產者和消費者的模式,主要有個例子,更好地理解下面的內容!

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class BoundedBuffer {
    final Lock lock = new ReentrantLock();
    // condition 依賴於 lock 來產生
    final Condition notFull = lock.newCondition();
    final Condition notEmpty = lock.newCondition();

    final Object[] items = new Object[100];
    int putptr, takeptr, count;

    // 生產
    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
            while (count == items.length)
                notFull.await();  // 佇列已滿,等待,直到 not full 才能繼續生產
            items[putptr] = x;
            if (++putptr == items.length) putptr = 0;
            ++count;
            notEmpty.signal(); // 生產成功,佇列已經 not empty 了,發個通知出去
        } finally {
            lock.unlock();
        }
    }

    // 消費
    public Object take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0)
                notEmpty.await(); // 佇列為空,等待,直到佇列 not empty,才能繼續消費
            Object x = items[takeptr];
            if (++takeptr == items.length) takeptr = 0;
            --count;
            notFull.signal(); // 被我消費掉一個,佇列 not full 了,發個通知出去
            return x;
        } finally {
            lock.unlock();
        }
    }
}
複製程式碼

上面只是貼個程式碼,先讓讀者知道怎麼用ReentrantLock和Condition,關於更多的細節,請讀者自行百度即可。

ConditionObject

Condition介面的實現類。Condition的方法依賴於ReentrantLock,而ReentrantLock又依賴於AbstractQueueSynchronized類。

內部結構

  private static final long serialVersionUID = 1173984872572414699L;
        /** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;
複製程式碼

條件佇列

在AQS中,其中執行緒等待的佇列我們成為阻塞佇列,這裡先引入條件佇列(condition queue),這裡先上圖看兩者的關係。

初步瞭解AQS是什麼(二)

區別

  1. 阻塞佇列和條件佇列都是node節點,也就是Node的例項。因為條件佇列的節點是需要轉移到阻塞佇列的。
  2. ReentrantLock是可以建立多個Condition例項的,則這裡會有condition1和condition2,並且ConditionObject中只有兩個和節點有關的屬性 firstWaiter和nextWaiter
  3. 每個Condition都有一個條件佇列與之關聯。當呼叫await()方法的時候,當先執行緒則會阻塞在當前地方,不會往下執行,然後將當前執行緒封裝成Node節點,新增到條件佇列的隊尾。
  4. 呼叫Condition.signal()會觸發喚醒。但是喚醒的是隊頭節點。也就是將對應執行緒的條件佇列的對頭(firstWaiter指向的節點)移到阻塞佇列的隊尾,等待獲取鎖。獲取鎖之後,await才會返回,然後繼續往下執行。

原始碼分析

await:讓執行緒掛起等待,並交出鎖

await():是可以被中斷的,呼叫這個方法的執行緒會阻塞,直到呼叫Signal()或者被中斷。

awaitUninterruptibly():不可被中斷,就是說有中斷訊號來了但不會響應。

public final void await() throws InterruptedException {
    		//剛開始的時候看看有沒有被中斷過,如果有就響應唄。
            if (Thread.interrupted())
                throw new InterruptedException();
    		// 把當前執行緒封裝為Node節點,新增到條件佇列的隊尾。
            Node node = addConditionWaiter();
    		//因為在await之前,這個執行緒肯定是擁有鎖的,所以這裡完全釋放鎖,為什麼要完全釋放鎖,是因為考慮到重入的問題。
    		//這裡的saveState返回的是釋放鎖之前的state值。
            int savedState = fullyRelease(node);
            int interruptMode = 0;
    		//這裡判斷的await的節點有沒有進入到阻塞佇列,也叫同步佇列。
    		//如果進入,返回true,不會執行while迴圈
    		//如果沒進入,就等著唄,等待其他執行緒叫醒你或者中斷訊號來臨唄。
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
    		//到了這一步,已經說明node節點在阻塞佇列了。那麼就爭搶資源,也就是AQS的爭搶資源的方式。
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
複製程式碼

addConditionWaiter:將條件佇列的節點加入阻塞佇列

  private Node addConditionWaiter() {
            Node t = lastWaiter; //指向條件佇列的尾節點
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {
                //如果進入到這裡,就說明條件佇列這時候不為空,但是尾節點取消了
                //因為在尾節點加入的時候,會把ws設施為CONDITION的。
                //先把取消等待的節點清除,然後再找到真正的尾節點
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
      		//之前說過,加入條件佇列的時候會把ws設定為CONDITION
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)//這裡說明條件佇列為空,也就是說第一次加入
                //設定好頭指標
                firstWaiter = node;
            else
                //那麼這裡就是條件佇列不為空唄,那就加入真正尾節點後頭就好了
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }
複製程式碼

在addConditionWaiter中,有一個unlinkCancelledWaiters()方法,顧名思義就是取消不等待的節點,因為條件佇列是單向連結串列,所以涉及到連結串列的操作。

就是說如果在await的時候,節點取消等待或者是說節點入隊的時候,發現最後一個節點已經取消了,那麼就呼叫一次這個方法

// 等待佇列是一個單向連結串列,遍歷連結串列將已經取消等待的節點清除出去
// 純屬連結串列操作,很好理解,看不懂多看幾遍就可以了
private void unlinkCancelledWaiters() {
    Node t = firstWaiter;
    Node trail = null;
    while (t != null) {
        Node next = t.nextWaiter;
        // 如果節點的狀態不是 Node.CONDITION 的話,這個節點就是被取消的
        if (t.waitStatus != Node.CONDITION) {
            t.nextWaiter = null;
            if (trail == null)
                firstWaiter = next;
            else
                trail.nextWaiter = next;
            if (next == null)
                lastWaiter = trail;
        }
        else
            trail = t;
        t = next;
    }
}
複製程式碼

fullyRelease: 完全釋放獨佔鎖

在await方法中,把節點加入到條件佇列之後,就需要完全釋放鎖了。因為考慮到重入的問題,所以必須要完全釋放。

例子:condition.await之前,當前節點就已經執行了兩次lock()操作,那麼state為2,也就是說擁有兩把鎖。那麼fullyRelease就應該設定state為0,返回2,返回沒釋放鎖之前所擁有的鎖的數量。然後再進行掛起。當被喚醒的時候,依舊需要兩把鎖才能進行執行下去。

final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
            if (release(savedState)) {
                failed = false;
                return savedState; //如果完全釋放鎖成功,那麼這裡返回的是釋放鎖之前所用有鎖的個數
            } else {
                //到這一步說明釋放鎖不成功,比如說有個沒有鎖的執行緒但是執行了釋放鎖,那麼肯定會到達這一步,所以會跑出異常
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                //到達這裡說明faile = true,也就是說release(savedState)返回的是false,丟擲了異常,說明該節點操作不當,那麼就得設定為取消,方便後面有節點加入的時候,觸發unlinkCancelledWaiters,把這個節點請出去
                node.waitStatus = Node.CANCELLED;
        }
    }
複製程式碼

isOnSyncQueue():判斷是否進入阻塞佇列

在await方法中,完全釋放鎖之後,這個方法會判斷該執行緒代表的佇列是否在阻塞佇列中,注意是阻塞佇列

前面我們也說過,在節點加入阻塞佇列的時候,會把ws 設定為Node.Condition

final boolean isOnSyncQueue(Node node) {
    	//如果在條件佇列的節點被移到阻塞佇列,那麼waitStatus會被設定為0,也就是在signal的時候,這個後面說
    	//所以,如果當前節點的waitStatus如果還是Condition的話,那麼肯定不在阻塞佇列中
    	//因為是把條件佇列的firstWaiter移動到阻塞佇列的,所以firstWaiter的pre是null,那麼肯定不在阻塞佇列中。
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        if (node.next != null) // If has successor, it must be on queue
            //這裡很簡單理解啦,在上面的前提下,如果next不為null,那麼肯定在阻塞佇列中啦,因為條件佇列用的是nextWaiter,阻塞佇列用的是next屬性
            return true;
    	//上面曾經試過用node.pre == null  判斷不在阻塞佇列中,那麼node.pre != null就可以判斷在阻塞佇列中了嗎?
    	//答案非也,因為在節點加入AQS的阻塞佇列的時候,會先將節點的pre設定為tail,然後再進行CAS操作將當前節點設定為tail節點,如果這個CAS操作失敗了,那麼此時tail節點並不是當前節點,那麼也說明該節點並不在佇列中
    	//所以這裡有一個從尾節點往前找的函式
        return findNodeFromTail(node);
    }


//這個函式很簡單,就是如果沒在佇列的話,那麼t的最終結果為null,返回false,如果找到就返回true唄
//這裡的不在佇列有種情況,就是上面說的CAS將當前節點設定為tail節點失敗,讀者可以整理下思路。
private boolean findNodeFromTail(Node node) 
        Node t = tail;
        for (;;) {
            if (t == node)
                return true;
            if (t == null)
                return false;
            t = t.prev;
        }
    }
複製程式碼

如果isOnSynchronized返回false的話,那麼就先掛起等待唄,也就是等待其他執行緒signal或者中斷 喚醒執行緒,將這個執行緒加入阻塞佇列!

signal:喚醒執行緒,讓執行緒得回鎖

前面說到執行緒掛起了,就等待著被喚醒唄!那麼這裡先說下signal函式,方便後面理解。

喚醒操作一般是由另外一個執行緒操作的,呼叫同一個物件的signal物件喚醒就好,其實喚醒的操作就是將節點從條件佇列移動到阻塞佇列中。

        public final void signal() {
            if (!isHeldExclusively())//如果執行緒沒有擁有排他鎖,也就是沒有獨佔鎖,丟擲異常唄
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                //到這裡就是找到條佇列的第一個節點,然後從這個節點找並沒有取消等待的執行緒!因為可能這時候firstWaiter這個節點已經取消等待了唄。
                doSignal(first);
        }

//----------------------------------------doSignal--------------------------------------
//從隊友找到第一個加入阻塞佇列的節點
//因為之前說過條件佇列的節點取消放棄等待
 private void doSignal(Node first) {
            do {
                //將firstWaiter指向first後面一個節點,因為現在這個firstWaiter將要離開佇列啦!
                if ( (firstWaiter = first.nextWaiter) == null)
                    //到這裡說明如果firstWaiter離開之後條件佇列都為空了,那麼也lastWaiter指標也置為null啦
                    lastWaiter = null;
                //斷開firstWaiter啦!
                first.nextWaiter = null;
            } while (!transferForSignal(first) && //這個while迴圈就是判斷這個first指向的節點是否轉移成功,如果返回false的話,那麼就是轉移不成功,也就是說取消了,那麼就查詢下一個節點。返回true就是說加入阻塞佇列成功啦,那麼這個函式就完成使命啦!
                     (first = firstWaiter) != null);
        }



//-----------------------------------------------transferForSignal---------------------
final boolean transferForSignal(Node node) {
       
   //這裡之前說過加入條件佇列的時候節點的waitStatus會初始化為Condition,如果這裡CAS更新失敗,說明狀態不為Condition,就說明取消了唄。那麼返回false,繼續尋找下一個想加入阻塞佇列的節點。
    //如果更新成功就把節點的ws設定為0,為初始態,就是為了加入阻塞佇列。
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
		
    //到達這裡說明CAS成功,ws也為0,那麼說明就可以自旋進入阻塞佇列啦!
        Node p = enq(node);//返回加入阻塞佇列後的前驅節點。
        int ws = p.waitStatus;
    	//這裡是根據前驅節點的狀態,如果ws>0,說明就是取消等待啦
    	// 如果ws < 0,就是說該節點沒有取消等待進入阻塞佇列。就先把加入阻塞佇列的節點的ws設定為SIGNAL,這個就不多說啦,具體看上一篇文章即可!
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            //到這裡說明節點要麼取消,要麼CAS想要加入阻塞佇列的節點的狀態失敗,那麼就先喚醒,為啥要喚醒呢?後面講!
            LockSupport.unpark(node.thread);
      	// 不管有沒有執行上一個if語句,都說明進入阻塞佇列已經成功啦!
        return true;
    }
複製程式碼

檢查中斷狀態

在signal之後,執行緒所代表的node節點肯定已經加入到阻塞佇列中啦!然後就準備去獲取鎖。

之前不是說到執行緒已經掛起了嗎?signal之後或者被中斷就已經喚醒啦

int interruptMode = 0;
while (!isOnSyncQueue(node)) {
    // 執行緒掛起
    LockSupport.park(this);

    if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
        break;
}
複製程式碼

有以下幾種情況會讓執行緒從LockSupport.park(this);這步往下走

  1. signal之後進入阻塞佇列,等待前驅節點釋放鎖,釋放鎖的時候就會呼叫 LockSupport.unPark(node.thread),也就是被喚醒啦!
  2. 當執行緒在park的時候,有其他執行緒對它進行了中斷。
  3. 在signal的時候,進行過進隊操作,但是前驅節點已經取消等待了或者CAS前驅節點的狀態為SIGNAL也失敗
  4. 假喚醒。這個也是存在的,和 Object.wait() 類似,都有這個問題

執行緒被喚醒之後,第一步就是執行checkInterruptWhileWaiting(node)這個函式啦,如果返回非0,就是說await的執行緒在park期間或者說signal之後(unpark之後)被中斷過,所以就要區分到底是signal之前中斷還是signal之後中斷。

如果返回0,那麼就是說執行緒在park的時候,沒有被中斷,被unpark就是在阻塞佇列正常被喚醒的!


checkInterruptWhileWaiting返回的值

  1. 如果在signal之前已經進行中斷,那麼返回THROW_IE -1
  2. 如果在signal之後已經進行中斷,那麼返回REINTERRUPT 1
  3. 如果返回0,那麼表示在park期間沒有進行中斷
 private int checkInterruptWhileWaiting(Node node) {
            return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }
複製程式碼

如何區分signal之前中斷還是之後中斷

主要在transferAfterCancelledWait這個方法裡,我們下面來看一下它的程式碼

final boolean transferAfterCancelledWait(Node node) {
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            //如果進行到這裡,那麼說明是signal之前就被中斷的。因為signal的時候,會把await在條件佇列的執行緒的waitStatus置為0,if判斷裡面的那個CAS操作就會失敗了。所以就說明如果到這裡,那麼肯定還在條件對了,並且沒有被signal
            enq(node);//從條件佇列進入阻塞佇列,注意這裡的nextWaiter並沒有置位空,如果後面還有節點,那麼也會一起帶去阻塞佇列
            return true;
        }
        /*
         * If we lost out to a signal(), then we can't proceed
         * until it finishes its enq().  Cancelling during an
         * incomplete transfer is both rare and transient, so just
         * spin.
         */
    	//到達這裡說明CAS失敗,有兩種情況
    	// 1. 是已經signal完成,已經把節點移到阻塞佇列中了,就不會進入下面那個while迴圈
    	// 2. 還沒有完全轉移到阻塞佇列中,那麼就進入while循壞咯,自旋等待,直到進入阻塞佇列為止,但是這種情況如翻譯所示,是非常罕見和稀少的
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
複製程式碼

所以經過上面的分析,只要是某個執行緒被中斷,那麼不管這個執行緒所代表的node節點是不是firstWaiter,也就是是不是會被signal,都會被喚醒,然後進入阻塞佇列。

獲取獨佔鎖

我們回到await函式,在上面的while退出之後,也就是我們的節點不管是被signal也好還是被中斷也好,都已經進入到阻塞佇列了,這點是毋庸置疑的。進入阻塞佇列後幹嘛呢?當然是為了獲取鎖啦,那麼如何獲取鎖呢?還記得上一節我們講的進入阻塞佇列之後如何獲取鎖麼?如果不記得就去看看吧!這裡放出連線

// acquireQueued上節已經分析過,不再綴訴。這裡判斷中斷是不是signal之後中斷的
//如果是signal前的,到後面就直接丟擲異常,不會進行這一步。
//如果是signal之後,但是在阻塞佇列等待的時候被中斷了,也就是說acquireQueued的時候被中斷過。前面退出while迴圈的時候可能是沒有中斷退出(interruptMode == 0),也可能是中斷退出,只要acquireQueued的時候被中斷過,並且是signal之後,都要重新設定下interruptMode,這裡讀者可以好好捋一下!
if (acquireQueued(node, savedState) && interruptMode != THROW_IE) 
                interruptMode = REINTERRUPT;
複製程式碼
if (node.nextWaiter != null) // clean up if cancelled
                //如果執行到這裡,那麼就是說是這個執行緒時被中斷加入阻塞佇列的,不是通過signal加入的,因為signal的話的nextWaiter早就已經被設定為null啦!
                unlinkCancelledWaiters();
           
複製程式碼
 if (interruptMode != 0) // 如果到這裡肯定是被中斷啦,管你是signal之前還是之後
     reportInterruptAfterWait(interruptMode);


//-----------------reportInterruptAfterWait------------------------------------------------------+
 private void reportInterruptAfterWait(int interruptMode)
            throws InterruptedException {
            if (interruptMode == THROW_IE) //就是這裡,如果signal之前就被中斷,丟擲異常!!
                throw new InterruptedException();
            else if (interruptMode == REINTERRUPT) //如果是signal之後中斷,那麼就設定下標誌位就好啊!讓開發者自己去寫外層邏輯去檢測!
                selfInterrupt();
        }


//---------------------------selfInterrupt------------------------------------
static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

複製程式碼

AQS 獨佔鎖的取消排隊

下面我們來分析下,如何取消鎖之間的競爭,當有幾個執行緒想競爭某個鎖的時候,我們希望有一個執行緒不去競爭這個鎖了。

在第一篇文章中我們用的lock,如果有中斷是不會丟擲異常的,只是標記一下狀態而已,具體的由外層函式決定。

在ReentrantLock中,有這樣的一個lock方法,可以檢測到中斷並且丟擲異常。

public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
//------------------------------------------------------------
public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();  //如果還沒開始競爭就中斷,也會丟擲異常返回
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

//-------------------------------doAcquireInterruptibly--------------------------------
 private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                 // 就是這裡,一旦異常,馬上結束這個方法,丟擲異常。
                // 這裡不再只是標記這個方法的返回值代表中斷狀態
                // 而是直接丟擲異常,而且外層也不捕獲,一直往外拋到 lockInterruptibly,
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

複製程式碼

上面我們看到,acquireInterruptibly裡面的doAcquireInterruptibly在競爭鎖的時候,一旦其他執行緒對其產生了中斷,那麼這個執行緒就馬上結束,不再去競爭。如果是acquired的話,如果在競爭鎖期間其他執行緒對其產生中斷,那麼先先搶得鎖再說,中斷後面再處理!這就是他們的區別啦!

總結

  1. 分析ReentrantLock公平鎖和非公平鎖的原始碼,理解其中的區別。
  2. 引入條件佇列,並且和阻塞佇列進行比較,分析他們的關係
  3. 對Condition的底層程式碼進行了部分的分析,主要是Node節點的await和signal的轉換以及如何處理中斷的問題
  4. 對AQS消除鎖競爭的兩種方式進行了原始碼分析,理解二者的不同

通過寫這篇部落格,對AQS的原始碼有了進一步的瞭解,本文主要花了大量的篇幅去寫ConditionObject,主要是為了弄明白Node節點在條件佇列和阻塞佇列的轉換的過程,同時自己由於之前對中斷不是很熟悉,所以補了一些知識,起碼比之前熟悉了一點,也算是一點小進步吧!後面會繼續瞭解AQS,下次見!

相關文章