Java可重入鎖AQS 和 CAS原理


  Java 實現同步的兩種方式,一種是使用synchronized關鍵字來實現同步訪問,另外一種是從Java 5之后,在java.util.concurrent.locks包下

提供了另外一種方式來實現同步訪問,那就是Lock。今天就來說一下Lock的實現類 ReentrantLock 的公平鎖模式下的實現方式。

java可重入鎖-ReentrantLock實現細節

ReentrantLock支持兩種獲取鎖的方式,一種是公平模型,一種是非公平模型。在繼續之前,咱們先把故事元素轉換為程序元素。

元素轉換 

咱們先來說說公平鎖模型:

初始化時, state=0,表示無人搶占了打水權。這時候,村民A來打水(A線程請求鎖),占了打水權,把state+1,如下所示:

線程A獲取鎖

線程A取得了鎖,把 state原子性+1,這時候state被改為1,A線程繼續執行其他任務,然后來了村民B也想打水(線程B請求鎖),線程B無法獲取鎖,生成節點進行排隊,如下圖所示:

線程B等待

初始化的時候,會生成一個空的頭節點,然后才是B線程節點,這時候,如果線程A又請求鎖,是否需要排隊?答案當然是否定的,否則就直接死鎖了。當A再次請求鎖,就相當於是打水期間,同一家人也來打水了,是有特權的,這時候的狀態如下圖所示:

可重入鎖獲取

到了這里,相信大家應該明白了什么是可重入鎖了吧。就是一個線程在獲取了鎖之后,再次去獲取了同一個鎖,這時候僅僅是把狀態值進行累加。如果線程A釋放了一次鎖,就成這樣了:

線程A釋放一次鎖

僅僅是把狀態值減了,只有線程A把此鎖全部釋放了,狀態值減到0了,其他線程才有機會獲取鎖。當A把鎖完全釋放后,state恢復為0,然后會通知隊列喚醒B線程節點,使B可以再次競爭鎖。當然,如果B線程后面還有C線程,C線程繼續休眠,除非B執行完了,通知了C線程。注意,當一個線程節點被喚醒然后取得了鎖,對應節點會從隊列中刪除。 

非公平鎖模型

如果你已經明白了前面講的公平鎖模型,那么非公平鎖模型也就非常容易理解了。當線程A執行完之后,要喚醒線程B是需要時間的,而且線程B醒來后還要再次競爭鎖,所以如果在切換過程當中,來了一個線程C,那么線程C是有可能獲取到鎖的,如果C獲取到了鎖,B就只能繼續乖乖休眠了。這里就不再畫圖說明了。

 

可重入鎖

  如果鎖具備可重入性,則稱作為可重入鎖。像synchronized和ReentrantLock都是可重入鎖,可重入性在我看來實際上表明了鎖的分配機制:基於線程的分配,而不是基於方法調用的分配。舉個簡單的例子,當一個線程執行到某個synchronized方法時,比如說method1,而在method1中會調用另外一個synchronized方法method2,此時線程不必重新去申請鎖,而是可以直接執行方法method2。

  看下面這段代碼就明白了:

class MyClass {
    public synchronized void method1() {
        method2();
    }
     
    public synchronized void method2() {
         
    }
}
 

   上述代碼中的兩個方法method1和method2都用synchronized修飾了,假如某一時刻,線程A執行到了method1,此時線程A獲取了這個對象的鎖,而由於method2也是synchronized方法,假如synchronized不具備可重入性,此時線程A需要重新申請鎖。但是這就會造成一個問題,因為線程A已經持有了該對象的鎖,而又在申請獲取該對象的鎖,這樣就會線程A一直等待永遠不會獲取到的鎖。

  而由於synchronized和Lock都具備可重入性,所以不會發生上述現象。

  接下來,開始真正的源碼分析:

  public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
  ReentrantLock的構造方法,現在只看公平鎖。FairSync的實現
static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

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

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        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方法時,會調用acquire(1)方法,獲取state,state等於0表示沒有線程獲取到鎖,接下來會判斷是否有等待的線程,如果沒有等待線程和進行cas操作state成功,則設置當前獨占線程為當前線程,返回true,獲取鎖成功。后面有個else條件就是可重入鎖的實現,如果獲取鎖失敗,則判斷當前獨占鎖線程是否為當前線程

,如果是當前線程,則將state進行加一操作,並返回true,獲取鎖成功。否則獲取鎖失敗。

注意事項:state 使用了volatile關鍵字,使state被線程修改時,及時的被其他線程讀取到最新的值。

AbstractQueuedSynchronizer

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

嘗試獲取鎖,當獲取鎖失敗后,將當前線程放入等待隊列中,然后開始循環去獲取鎖操作。
final boolean acquireQueued(final Node node, int arg) {
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;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

如果已經沒有線程獲取鎖的時候,則返回true,當前鎖線程進行interrupt操作,

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM