ReentrantLock源碼剖析二(AQS)

一、簡介

? ? ? ?上一篇文章講到,ReentrantLock方法的實現全部是依靠Sync的方法。而Sync又是繼承了AQS,所以需要重點分析AQS。
? ? ? ?AQS的設計是采用模板方法模式的。即如果要使用AQS,就需要繼承AQS并重寫AQS里指定的方法,以下方法可以按照需要被重寫:

protected boolean tryAcquire(int arg) {
    throw new UnsupportedOperationException();
}

? ? ? ?tryAcquire(int arg), 獨占式獲取同步狀態,實現該方法需要查詢當前狀態并判定同步狀態是否符合預期,然后再進行CAS設置同步狀態。

protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

? ? ? ?tryRelease(int arg), 獨占式釋放同步狀態。

protected int tryAcquireShared(int arg) {
    throw new UnsupportedOperationException();
}

? ? ? ?tryAcquireShared(int arg), 共享式獲取同步狀態,返回大于等于0的值,表示獲取成功,反之獲取失敗。

protected boolean tryReleaseShared(int arg) {
    throw new UnsupportedOperationException();
}

? ? ? ?tryReleaseShared(int arg), 共享式釋放同步狀態。

protected boolean isHeldExclusively() {
    throw new UnsupportedOperationException();
}

? ? ? ?isHeldExclusively(), 當前AQS是否在獨占模式下被線程占用,一般該方法表示是否被當前線程獨占。
? ? ? ?而重寫這些方法的時候需要操作AQS中的state變量。該變量是一個int型的volatile變量。具有volatile讀和volatile寫的內存語義。
AQS提供了以下方法來操作 state

protected final int getState() {
    return state;
}
protected final void setState(int newState) {
    state = newState;
}
//原子性設置state的值
protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update)
}

二、同步隊列及Node節點

? ? ? ?通常情況下,當線程獲取同步狀態(調用lock())失敗時,會被掛起(為什么是通常情況下,因為還有tryLock()方法,該方法獲取鎖失敗時會返回false)。那么AQS是怎么維持這些被阻塞線程的信息的呢?答案是,同步隊列,這是一個FIFO雙向隊列。AQS會將當前線程以及等待狀態等信息構造成一個Node節點并加入到同步隊列。當同步狀態(鎖)被釋放時,會將阻塞隊列的首節點的線程喚醒,使其可以再次嘗試獲取同步狀態。
? ? ? ?Node節點是構成同步隊列的基礎,AQS擁有首節點和尾節點,沒有成功獲取同步狀態的線程將會被構造成一個節點加入到隊列的尾部。
? ? ? ?看一下Node的定義,它是AQS的靜態內部類:

static final class Node {
        /**一個標記,指明節點在共享模式下等待同步狀態 */
        static final Node SHARED = new Node();
        /** 一個標記,指明節點在獨占模式下等待同步狀態*/
        static final Node EXCLUSIVE = null;
        //以上兩個值是 nextWaiter 域的取值

        //以下四個值是waitStatus的可能取值。
        /** 在同步隊列中等待的線程由于超時或被中斷,將waitStatus標記為CANCELLED,意思是取消等待同步狀態 */
        static final int CANCELLED =  1;

        /**表明當前節點如果釋放了狀態或者被取消,則喚醒后繼節點中的線程,使得后繼節點的線程得以運行 */
        static final int SIGNAL    = -1;

        /** 表明節點在等待一個Condition的signal,該節點處于等待隊列中, 當其他線程調用signal()方法時,
         *該節點將會從等待隊列中轉移到同步隊列,加入到對同步狀態的獲取中*/
        static final int CONDITION = -2;

        /**
         *表示下一次共享式同步狀態的獲取將會無條件的傳播下去。
         *表明一個共享鎖的釋放將會傳播到其他節點,而不是僅僅后繼節點。
         *這個狀態僅僅在 doReleaseShared()中對頭節點進行設置。
         */
        static final int PROPAGATE = -3;

        // 取值為上面的1、-1、-2、-3,或者0(以后會講到)
        // 這么理解,只需要知道如果這個值大于 0,代表此線程取消了等待,
        // 也許就是說半天搶不到鎖,不搶了,ReentrantLock是可以指定timeouot的。。。
        volatile int waitStatus;
        //前一個節點
        volatile Node prev;
        //后一個節點
        volatile Node next;
        //線程
        volatile Thread thread;
        //指向下一個正在等待Condition變量的節點,或者取值為SHARED、EXCLUSIVE
        Node nextWaiter;
    }

? ? ? ?了解了Node節點之后,我們來看看AQS中都有哪些重要的屬性:

// 頭結點,可以把它當做當前持有鎖的線程,注意:阻塞隊列并不包含head節點
private transient volatile Node head;
// 尾節點,每個新的節點進來,都插入到最后
private transient volatile Node tail;
// 代表當前鎖的狀態,0代表沒有被占用,大于0代表有線程持有當前鎖
// 之所以說大于0,而不是等于1,是因為重入鎖,每次重入都加上1
private volatile int state;
// 代表當前持有獨占鎖的線程,舉個最重要的使用例子,因為鎖可以重入
// reentrantLock.lock()可以嵌套調用多次,所以每次用這個來判斷當前線程是否已經擁有了鎖
// if (currentThread == getExclusiveOwnerThread()) {state++}
private transient Thread exclusiveOwnerThread; //繼承自AbstractOwnableSynchronizer

三、鎖的獲取

? ? ? ?直接看代碼:

public void lock(){
     sync.lock();
}

其中sync既可以是FairSync,也可以是NonfairSync。區別主要體現在:

//NonFairSync
final void lock() {
      //不考慮阻塞隊列中有沒有其他線程在等待鎖的釋放,直接進行加鎖,成功后則設置本線程為鎖的獨占線程。
      if (compareAndSetState(0, 1))
          setExclusiveOwnerThread(Thread.currentThread());
      else
          acquire(1);  //AQS中的實現
}
//FairSync
final void lock() {
      acquire(1);    //AQS中的實現
}

? ? ? ?兩者都調用了 AQS 的 void acquire(int arg) 方法:

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

其中tryAcquire(arg)方法是Sync的子類FairSyncNonfairSync重寫的方法,正如名字所示,它是嘗試進行加鎖,成功返回true,失敗返回false,此時還沒有加入阻塞隊列一說。

//FairSync
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;
}
//NonfairSync
protected final boolean tryAcquire(int acquires) {
      return nonfairTryAcquire(acquires);
}
//Sync
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;
}

如果加鎖成功,tryAcquire(int arg)返回true,就沒有后續的步驟了。如果失敗,則就要進入阻塞隊列了:

public final void acquire(int arg) {    //arg = 1 
    if (!tryAcquire(arg) &&        //此時tryAcquire返回true
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))      //執行此步驟
        selfInterrupt();
}

看看addWaiter(Node.EXCLUSIVE)具體做了些什么:

//addWaiter做了兩件事:1、封裝線程,構造出Node結點;2、入隊
private Node addWaiter(Node mode) {    //mode = Node.EXCLUSIVE,
        //前面說過,Node.EXCLUSIVE和Node.SHARED是兩個標記,
        //標記Node節點獲取鎖的模式
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 仔細看看上面的代碼,代碼執行到這里,
        // 說明 pred==null(隊列是空的) 或者 CAS失敗(有線程在競爭入隊)
        enq(node);
        return node;
}

//采用自旋的方式加入隊列,也就是在CAS設置tail過程中,競爭一次競爭不到,我就多次競爭
private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                //隊列為空時,初始化一個頭節點,完了之后,再次進入for循環,執行下面的else語句,
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
}

再次回到下面這個代碼,addWaiter將由當前線程構成的Node結點加入隊列之后,就執行acquireQueued(Node node, int arg)方法,

public final void acquire(int arg) {    //arg = 1 
    if (!tryAcquire(arg) &&        //tryAcquire返回true
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))      //此時addWaiter已經將node結點加入了隊尾
        selfInterrupt();
}

final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;    
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();  //當前結點的前驅結點
                //如果前驅結點是頭節點,則可以嘗試獲取鎖,
                // p==head 說明當前節點雖然進入了阻塞隊列,但它是阻塞隊列的第一個,因為它的前驅是head
                // 注意,阻塞隊列不包含head節點,head一般指的是占有鎖的線程,head后面的才稱為阻塞隊列
                // 所以當前節點可以去試搶一下鎖,那么為什么可以試著搶一下呢
                //首先,它是阻塞隊列的隊首,其次,前面的head有可能是一個空節點,什么意思,
                //前面enq方法中說過,當發現隊列為空時(tail == null),會compareAndSetHead(new Node()),
                //這時會構造一個什么也沒有的空節點,沒有設置任何線程
                // 也就是說,當前的head不屬于任何一個線程,所以作為隊頭,可以去試一試
                if (p == head && tryAcquire(arg)) {
                    //此時獲取鎖成功,將head設置為當前獲取鎖的結點
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 到這里,說明上面的if分支沒有成功,要么當前node本來就不是隊頭,
                // 要么就是tryAcquire(arg)沒有搶贏別人,此時按照我們的理解,應該將線程掛起了,因為此時別人在占著鎖呢,。
                // 按字面意思就是,檢查在失敗獲取鎖之后,是否應該將線程掛起,如果可以,那么就掛起線程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
}

下面看一下shouldParkAfterFailedAcquire(p, node)

// 剛剛說過,會到這里就是沒有搶到鎖唄,這個方法說的是:"當前線程沒有搶到鎖,是否需要掛起當前線程?"
 // 第一個參數是前驅節點,第二個參數才是代表當前線程的節點
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            //前驅結點的waitAtatus是SIGNAL(-1),說明當前線程可以安心掛起了,
            //因為前驅結點在釋放鎖的時候,會signal到后驅結點,將其喚醒
            return true;
        if (ws > 0) {
           //此時說明前驅結點已經取消等待鎖(因為超時或者被中斷)
           // 所以下面這塊代碼說的是將當前節點的prev指向waitStatus <= 0的節點,
           // 簡單說,就是為了找個好爹,因為你還得依賴它來喚醒呢,如果前驅節點取消了排隊,
           // 找前驅節點的前驅節點做爹,往前循環總能找到一個好爹的
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            //此時waitStatus = 0、-2、-3,將前置節點的waitStatus設置為SIGNAL
            //然后返回false,回到外層的acquireQueued重新進入for循環獲取鎖
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
}
// 返回到前面是這個方法
// if (shouldParkAfterFailedAcquire(p, node) &&
//                parkAndCheckInterrupt())
//        interrupted = true;      
//如果shouldParkAfterFailedAcquire返回true,說明線程應該被掛起
//此時前驅節點的waitStatus==-1,是正常情況,那么當前線程需要被掛起,等待以后被喚醒
//此時再執行掛起線程的操作
private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);    //線程在這里被掛起,等待被喚醒,在此狀態下,有兩種途徑可以喚醒該線程:1)被unpark();2)被interrupt()。
        return Thread.interrupted();        //此時線程被喚醒,就檢查是不是被中斷的
}

如果在整個等待過程中被中斷過,則返回true,否則返回false。
? ? ? ?如果線程在等待過程中被中斷過,它是不響應的。只是獲取資源后才再進行自我中斷 selfInterrupt(),將中斷補上。
? ? ? ?回頭再重點看一下 acquireQueued() 的代碼:

    final boolean acquireQueued(final Node node, int arg) {
        //標記是否成功獲取同步狀態,成功為false。
        boolean failed = true;
        try {
            boolean interrupted = false;//標記等待過程中是否被中斷過
            for (;;) {      //自旋狀態
                final Node p = node.predecessor();
                //如果前一個節點是頭節點,就在自旋過程中再次獲取同步狀態
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //前一節點不是頭節點或者獲取狀態失敗,就檢查當前線程是否可以進入   
                //waiting狀態,如果可以,就park當前線程,進入waiting狀態,并檢查中斷。
                //在此狀態下,有兩種途徑可以喚醒該線程:1)被unpark();2)被interrupt()。
                if (shouldParkAfterFailedAcquire(p, node) &&
                                            parkAndCheckInterrupt())    
                     //如果線程是被中斷喚醒的, interrupted = true
                     //此時線程并不響應中斷,而是繼續for循環獲取鎖,獲取到鎖之后
                     //再進行中斷,具體邏輯看下面
                    /*
                    public final void acquire(int arg) {    
                          if (!tryAcquire(arg) &&        
                                  acquireQueued(addWaiter(Node.EXCLUSIVE), arg))                  
                             //此時acquiredQueued返回interrupted為true,進行自我中斷
                             selfInterrupt();
                    }

*/
                    interrupted = true;       
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

? ? ? ?線程被喚醒的時候,就去檢查中斷標志位有沒有置位,如果置位則返回true,繼而 boolean acquireQueued(final Node node, int arg)返回true,繼而在 final void acquire(int arg) 中就執行 selfInterrupt(),進行自我中斷。

四.鎖的釋放

? ? ? ?unlock()方法調用syncrelease(int arg)方法

public void unlock() {
        sync.release(1);
}
//AQS
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;   //找到頭節點
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);    //喚醒等待隊列里的下一個線程
        return true;
    }
    return false;
}
//Sync
protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;   //是否完全釋放鎖
            if (c == 0) {      //鎖被完全釋放
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
}

head結點釋放完鎖時,此時沒有線程占有鎖,緊接著去喚醒后面的線程, unparkSuccessor(h)就是做這樣的事:

  private void unparkSuccessor(Node node) {
      //這里,node一般為當前線程所在的結點。
      int ws = node.waitStatus;
      if (ws < 0)//置零當前線程所在的結點狀態,允許失敗。
          compareAndSetWaitStatus(node, ws, 0);
  
      Node s = node.next;//找到下一個需要喚醒的結點s
      if (s == null || s.waitStatus > 0) {//如果為空或已取消
          s = null;
         for (Node t = tail; t != null && t != node; t = t.prev)
             if (t.waitStatus <= 0)//從這里可以看出,<=0的結點,都是還有效的結點。
                 s = t;
     }
     if (s != null)
         LockSupport.unpark(s.thread);//喚醒
 }

線程被喚醒以后,被喚醒的線程繼續執行下面的代碼:

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this); // 剛剛線程被掛起在這里了
    return Thread.interrupted();
}
// 又回到這個方法了:acquireQueued(final Node node, int arg),這個時候,node結點的線程去爭取鎖,成功的話node就變為head了

五.其他

1、可中斷的獲取鎖

? ? ? ?線程在等待鎖的時候可以被中斷,拋出異常

public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
}
//AQS
    public final void acquireInterruptibly(int arg) throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))          //嘗試獲取鎖,如果返回false,即獲取鎖失敗,執行下面
            doAcquireInterruptibly(arg);
    }
//AQS
    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())
                    throw new InterruptedException();   //重點在這里,直接拋出異常
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

它和不響應中斷版本的函數很像,只是在這里,如果線程被中斷過,直接拋出異常。

2、嘗試獲取鎖

? ? ? ?嘗試獲取鎖,成功返回true,失敗立即返回false,不掛起線程。該方法在沒有其他線程占有鎖的情況下就去獲取鎖,即使是公平鎖,不會考慮阻塞隊列中有沒有其他線程在等待該鎖。代碼比較簡單。

public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
}
//AQS
 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;
}

3、帶超時的獲取鎖

? ? ? ?在規定的時間內獲取鎖,成功返回true,超時返回false,可被中斷,拋出中斷異常,中斷標志位清空。如果時間參數小于等于0,立即返回結果,不掛起線程。

public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
//AQS
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||      //嘗試獲取鎖,失敗的話進入下面
            doAcquireNanos(arg, nanosTimeout);
}
//AQS
 private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;      //直接返回失敗
        final long deadline = System.nanoTime() + nanosTimeout;   //未來超時的時候
        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 true;
                }
                nanosTimeout = deadline - System.nanoTime();  //剩余時間
                if (nanosTimeout <= 0L)
                    return false;   //如果剩余時間小于等于0,還沒有獲取到鎖,返回失敗
                if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold)      
                //spinForTimeoutThreshold是一個閾值,剩余時間超過該閾值時才會
                //去定時掛起線程知道剩余時間流逝完,否則就采取自旋的方式消耗剩余時間
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
}

? ? ? ?至此,ReentrantLock所有加鎖解鎖相關的方法都已經分析的差不多了,其實可以看出,核心的代碼也就那些,弄懂了之后其他的就不難了,主要還是掌握阻塞隊列的設計思想。其他一些不常用的方法大家自己去看看就行,不是很難。就到這吧,明天繼續,晚安。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容