JAVA併發編程:詳解AQS、顯示鎖Lock、ReentrantLock及源碼分析

1、AbstractQueuedSynchronizer

1.1 學習 AQS 的必要性

  隊列同步器 AbstractQueuedSynchronizer(以下簡稱同步器或 AQS),是用來構建鎖或者其他同步組件的基礎框架,它使用了一個 int 成員變量表示同步狀態 state,通過內置的 FIFO 隊列來完成資源獲取線程的排隊工作。

1.2 AQS使用方式

  AQS 的主要使用方式是繼承,子類通過繼承 AQS 並實現它的抽象方法來管 理同步狀態,在 AQS 裏由一個 int 型的 state 來代表這個狀態,在抽象方法的實 現過程中免不了要對同步狀態進行更改,這時就需要使用同步器提供以下 3 個方法來進行操作,因爲它們能夠保證狀態的改變是安全的。

  • int getState():返回當前線程同步狀態state的值
  • setState(int newState):設置同步狀態state值,state屬性被volatile修飾。
  • compareAndSetState(int expect, int update):如果當前狀態state值value等於期望值expect,那麼原子地設置同步狀態state爲給定update值。使用 CAS 設置當前狀態,該方 法能夠保證狀態設置的原子性。
/**
 * The synchronization state.
*/
private volatile int state;

  在實現上,子類推薦被定義爲自定義同步組件的靜態內部類,AQS 自身沒有 實現任何同步接口,它僅僅是定義了若干同步狀態獲取和釋放的方法來供自定義 同步組件使用,同步器既可以支持獨佔式地獲取同步狀態,也可以支持共享式地 獲取同步狀態,這樣就可以方便實現不同類型的同步組件,比如ReentrantLock、 ReentrantReadWriteLock 和 CountDownLatch等等。

1.3 AQS中的設計模式

  同步器的設計基於模板方法模式。模板方法模式的意圖是,定義一個操作中 的算法的骨架,而將一些步驟的實現延遲到子類中。模板方法使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。我們最常見的就是 Spring 框架裏的各種 Template。

1.4 AQS中的方法

1.4.1 模板方法

  實現自定義同步組件時,將會調用同步器提供的模板方法。這些模板方法同步器提供的模板方法基本上分爲以下3 類:獨佔式獲取與釋放同 步狀態、共享式獲取與釋放、同步狀態和查詢同步隊列中的等待線程情況。

  • acquire(int arg):獨佔式獲取同步狀態,如果當前線程獲取同步狀態成功,則由該方法返回,否則,將會進入同步隊列等待,該方法將會調用重寫的tryAcquire(int arg)方法。
  • acquireInterruptibly(int arg):與acquire(int arg)相同,但是該方法響應中斷,當前線程未獲取同步狀態而進入同步隊列中,如果當前線程被中斷,則該方法會拋出InterruptedException並返回。
  • tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException:在 acquireInterruptibly(int arg)方法的基礎上加了超時限制,如果當前線程在超時時間內沒有沒有獲取同步狀態,則返回false,獲取到則返回true。
  • acquireShared(int arg) :共享式獲取同步狀態,如果當前線程未獲取同步狀態,將會進入同步隊列等待,與獨佔式獲取的區別是在同一時刻有多個線程獲取到同步狀態。
  • acquireInterruptibly(int arg) throws InterruptedException :與acquireShared(int arg) 相同,該方法響應中斷。
  • tryAcquireSharedNanos(int arg, long nanosTimeout) throws InterruptedException:在acquireInterruptibly(int arg)的基礎上增加了超時限制。
  • release(int arg):獨佔式的獲取同步狀態,該方法會在釋放同步狀態之後,將同步隊列中等一個節點包含的線程喚醒。
  • releaseShared(int arg):共享式的釋放同步狀態。
  • Collection getQueuedThreads():獲取等待在同步隊列上的線程集合。

1.4.2 可重寫的方法

  • tryAcquire(int arg):獨佔式獲取同步狀態,實現給方法需要查詢查詢當前狀態並判斷同步狀態是否符合預期,然後再進行CAS操作設置同步狀態。
  • tryRelease(int arg):獨佔式是否同步狀態,等待獲取同步狀態的線程將有機會獲取同步狀態。
  • tryAcquireShared(int arg):共享式獲取同步狀態,返回大於等於0的值,表示獲取成功,否則獲取失敗。
  • tryReleaseShared(int arg):共享式是否同步狀態。
  • isHeldExclusively():當前同步器是否在獨佔模式下被線程佔用,一般該方法表示是否被當前線程所獨佔。

1.5 AQS中的數據結構-節點和同步隊列

1.5.1 節點Node

  既然說Java 中的AQS 是CLH 隊列鎖的一種變體實現,毫無疑問,作爲隊列來說,必然要有一個節點的數據結構來保存我們前面所說的各種域,比如前驅節點,節點的狀態等,這個數據結構就是AQS中的內部類Node。
作爲這個數據結構應該關心些什麼信息?

  • 線程信息,肯定要知道我是哪個線程。
  • 隊列中線程狀態,既然知道是哪一個線程,肯定還要知道線程當前處在什麼狀態,是已經取消了“獲鎖”請求,還是在“等待中”,或者說“即將得到鎖”。
  • 前驅和後繼線程,因爲是一個等待隊列,那麼也就需要知道當前線程前面的是哪個線程,當前線程後面的是哪個線程(因爲當前線程釋放鎖以後,理當立馬通知後繼線程去獲取鎖)。
    下面來看看這個Node類的設計,如下圖所示:
    在這裏插入圖片描述
      從Node類中可以看出,其中包括了:線程的兩種等待模式、線程在隊列中的狀態及一些成員變量。

線程的2種等待模式:

  • SHARED:表示線程以共享模式等待鎖(如ReadLock)
  • EXCLUSIVE:表示線程獨佔模式等待鎖(如ReetrantLock),獨佔模式就是一把鎖只能由一個線程持有,不能同時存在多個線程使用同一個鎖。

線程在隊列中的狀態:

  • CANCELLED:值爲1,表示線程的獲鎖請求已經“取消”。
  • SIGNAL:值爲-1,表示該線程一切都準備好了,就等待鎖空閒出來給此線程了。
  • CONDITION:值爲-2,表示線程等待某一個條件(Condition)被滿足。
  • PROPAGATE:值爲-3,當線程處在“SHARED”模式時,該字段纔會被使用上。

成員變量:

  • volatile int waitStatus:該int變量表示線程在隊列中的狀態,其值就是上述提到的CANCELLED、SIGNAL、CONDITION、PROPAGATE。
  • volatile Node prev:該變量類型爲Node對象,表示該節點的前一個Node 節點(前驅)
  • volatile Node next:該變量類型爲Node對象,表示該節點的後一個Node 節點(後繼)
  • volatile Thread thread:該變量類型爲Thread 對象,表示該節點的代表的線程
  • Node nextWaiter:該變量類型爲Node對象,表示等待condition 條件的Node節點。

  當前線程獲取同步狀態失敗時,同步器會將當前線程以及等待狀態等信息構造成爲一個節點(Node)並將其加入同步隊列,同時會阻塞當前線程,當同步狀態釋放時,會把首節點中的線程喚醒,使其再次嘗試獲取同步狀態。同步隊列中的節點(Node)用來保存獲取同步狀態失敗的線程引用、等待狀態以及前驅和後繼節點。

1.5.2 同步隊列

head 和tail:
  AQS 還擁有首節點(head)和尾節點(tail)兩個引用,一個指向隊列頭節點,而另一個指向隊列尾節點。
  因爲首節點head 是不保存線程信息的節點,僅僅是因爲數據結構設計上的需要,在數據結構上,這種做法往往叫做“空頭節點鏈表”。對應的就有“非空頭結點鏈表”。
在這裏插入圖片描述
節點加入到同步隊列:
  當一個線程成功地獲取了同步狀態(或者鎖),其他線程將無法獲取到同步 狀態,也就是獲取同步狀態失敗,AQS 會將這個線程以及等待狀態等信息構造成 爲一個節點(Node)並將其加入同步隊列的尾部。而這個加入隊列的過程必須要保證線程安全,因此同步器提供了一個基於 CAS 的設置尾節點的方法: compareAndSetTail(Node expect,Nodeupdate),它需要傳遞當前線程“認爲”的尾節點和當前節點,只有設置成功後,當前節點才正式與之前的尾節點建立關聯。
在這裏插入圖片描述
首節點的變化:
  首節點是獲取同步狀態成功的節點,首節點的線程在釋放同步狀態時,將會 喚醒後繼節點,而後繼節點將會在獲取同步狀態成功時將自己設置爲首節點。設 置首節點是通過獲取同步狀態成功的線程來完成的,由於只有一個線程能夠成功 獲取到同步狀態,因此設置頭節點的方法並不需要使用 CAS 來保證,它只需要將 首節點設置成爲原首節點的後繼節點並斷開原首節點的 next 引用即可。
在這裏插入圖片描述

2、顯示鎖

  有了 synchronized 爲什麼還要 Lock?
  Java 程序是靠 synchronized 關鍵字實現鎖功能的,使用 synchronized 關鍵字將會隱式地獲取鎖,但是它將鎖的獲取和釋放固化了,也就是先獲取再釋放。

顯示鎖的特性:

  • 嘗試非阻塞地獲取鎖:當前線程嘗試獲取鎖,如果這一時刻鎖沒有被其他線程獲取到,則成功獲取並持有鎖。
  • 能被中斷地獲取鎖:與synchronized關鍵字不同,獲取到鎖的線程能夠響應中斷,當獲取到鎖的線程被中斷時,中斷異常將會被拋出,同時鎖會被釋放。
  • 超時獲取鎖:在指定的截止時間之前獲取鎖,如果截止時間到了仍舊無法獲取鎖,則返回空。

3、Lock接口

3.1 Lock接口常用API

  • void lock():獲取鎖,調用該方法當前線程將會獲取鎖,當鎖獲得後,從改方法返回。
  • void lockInterruptibly() throws InterruptedException:可中斷地獲取鎖,和lock()方法不同之處在於該方法響應會中斷,即在鎖的獲取過程中,可以中斷當前線程。
  • boolean tryLock():嘗試非阻塞地獲取鎖,調用該方法後立即返回,如果能夠獲取鎖則返回true,否則返回false。
  • boolean tryLock(long time, TimeUnit unit) throws InterruptedException:超時獲取鎖,當前線程在以下3中情況會返回。當前線程在超時時間內獲取得了鎖,則返回true;當前線程在超時時間內被中斷,則返回false;超時時間結束,則返回false。
  • void unlock():釋放鎖

3.2 Lock的標準用法

private static Lock lock = new ReentrantLock();
private static void lock(){
    lock.lock();
    try{
        System.out.println("execute business 。。。");
    }finally {
        lock.unlock();
    }
}

  在finally 塊中釋放鎖,目的是保證在獲取到鎖之後,最終能夠被釋放。
  不要將獲取鎖的過程寫在try 塊中,因爲如果在獲取鎖(自定義鎖的實現)時發生了異常,異常拋出的同時,也會導致鎖無故釋放。

4、可重入鎖ReentrantLock、鎖的公平和非公平

4.1 可重入鎖

  可重入鎖,簡單來說就“同一個線程對於已經獲得的鎖,可以多次繼續申請到該鎖的使用權”。而synchronized 關鍵字隱式的支持可重入,比如一個synchronized修飾的遞歸方法,在方法執行時,執行線程在獲取了鎖之後仍能連續多次地獲得該鎖。ReentrantLock 在調用lock()方法時,已經獲取到鎖的線程,能夠再次調用lock()方法獲取鎖而不被阻塞。
  重進入是指任意線程在獲取到鎖之後能夠再次獲取該鎖而不會被鎖所阻塞, 該特性的實現需要解決以下兩個問題。
  1)線程再次獲取鎖。鎖需要去識別獲取鎖的線程是否爲當前佔據鎖的線程, 如果是,則再次成功獲取。
  2)鎖的最終釋放。線程重複 n 次獲取了鎖,隨後在第 n 次釋放該鎖後,其 他線程能夠獲取到該鎖。鎖的最終釋放要求鎖對於獲取進行計數自增,計數表示 當前鎖被重複獲取的次數,而鎖被釋放時,計數自減,當計數等於 0 時表示鎖已 經成功釋放。
  nonfairTryAcquire 方法增加了再次獲取同步狀態的處理邏輯:通過判斷當前 線程是否爲獲取鎖的線程來決定獲取操作是否成功,如果是獲取鎖的線程再次請 求,則將同步狀態值進行增加並返回 true,表示獲取同步狀態成功。同步狀態表 示鎖被一個線程重複獲取的次數。
  如果該鎖被獲取了 n 次,那麼前(n-1)次 tryRelease(int releases)方法必須返回 false,而只有同步狀態完全釋放了,才能返回 true。可以看到,該方法將同步狀 態是否爲 0 作爲最終釋放的條件,當同步狀態爲 0 時,將佔有線程設置爲null, 並返回 true,表示釋放成功。

4.2 公平鎖和非公平鎖

  如果在時間上,先對鎖進行獲取的請求一定先被滿足,那麼這個鎖是公平的,反之,是不公平的。公平的獲取鎖,也就是等待時間最長的線程最優先獲取鎖,也可以說鎖獲取是順序的。 事實上,公平的鎖機制往往沒有非公平的效率高。
  ReentrantLock 的構造函數中,默認的無參構造函數將會把 Sync 對象創建爲 NonfairSync 對象,這是一個“非公平鎖”;而另一個構造函數 ReentrantLock(boolean fair)傳入參數爲 true 時將會把 Sync 對象創建爲“公平鎖” FairSync。
  nonfairTryAcquire(int acquires)方法,對於非公平鎖,只要 CAS 設置同步狀態 成功,則表示當前線程獲取了鎖,而公平鎖則不同。tryAcquire 方法,該方法與 nonfairTryAcquire(int acquires)比較,唯一不同的位置爲判斷條件多了 hasQueuedPredecessors()方法,即加入了同步隊列中當前節點是否有前驅節點的 判斷,如果該方法返回 true,則表示有線程比當前線程更早地請求獲取鎖,因此 需要等待前驅線程獲取並釋放鎖之後才能繼續獲取鎖。
  在激烈競爭的情況下,非公平鎖的性能高於公平鎖的性能的一個原因是:在恢復一個被掛起的線程與該線程真正開始運行之間存在着嚴重的延遲。假設線程A持有一個鎖,並且線程B 請求這個鎖。由於這個鎖已被線程A 持有,因此B 將被掛起。當A 釋放鎖時,B 將被喚醒,因此會再次嘗試獲取鎖。與此同時,如果C 也請求這個鎖,那麼C 很可能會在B 被完全喚醒之前獲得、使用以及釋放這個鎖。這樣的情況是一種“雙贏”的局面:B 獲得鎖的時刻並沒有推遲,C 更早地獲得了鎖,並且吞吐量也獲得了提高。

5、ReentrantLock源碼分析

  ReentrantLock類是基於AbstractQueuedSynchronizer(AQS或同步器,後續會專門講解AQS相關知識點)隊列同步器實現的,當調用ReentrantLock類的lock()方法時,會調用其內部對象Sync的lock()方法。
ReentrantLock默認是非公平鎖,但是ReentrantLock的構造方法提供了一個參數可以指定鎖是公平鎖還是非公平鎖。

// 默認非公平鎖
public ReentrantLock() {
    sync = new NonfairSync();
}

// 通過參數fair爲true,可以指定獲取的鎖爲公平鎖
public ReentrantLock(boolean fair) {
  sync = fair ? new FairSync() : new NonfairSync();
}

如下圖所示,可以清晰的看到各類的關係。
在這裏插入圖片描述

5.1 公平鎖 FairSync

5.1.1 lock()方法

  FairSync 類 lock()方法會調用ASQ中 acquire(int arg)方法,可以獲取同步狀態,主要完成了同步狀態獲取、節點構造、加入同步隊列以及在同步隊列中自旋等待的相關工作。
  首先調用自定義同步器實現的 tryAcquire(int arg)方法,該方法需要保證線程 安全的獲取同步狀態。
  如果同步狀態獲取失敗(tryAcquire 返回 false),則構造同步節點(獨佔式 Node.EXCLUSIVE,同一時刻只能有一個線程成功獲取同步狀態)並通過 addWaiter(Node node)方法將該節點加入到同步隊列的尾部。
  最後調用 acquireQueued(Node node,int arg)方法,使得該節點以“死循環” 的方式獲取同步狀態。如果獲取不到則阻塞節點中的線程,而被阻塞線程的喚醒 主要依靠前驅節點的出隊或阻塞線程被中斷來實現。

public final void acquire(int arg) {
	 // 嘗試獲取鎖,如果獲取鎖失敗,則將當前線程執行信息放入到AQS內部的隊列中
     if (!tryAcquire(arg) &&
         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
         // 中斷線程
         selfInterrupt();
 }

FairSync類中實現AQS中的 tryAcquire(int arg)方法,如下所示:

/**
  * 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();
     // 獲取線程同步狀態state
     int c = getState();
     // 如果狀態爲0,表示當前鎖是空閒的,可以獲取鎖
     if (c == 0) {
     	 // 判斷當前線程是不是等待最久多線程,判斷當前線程是不是在等待隊列的第二個元素,因爲隊列head是當前(之前)擁有鎖的線程,如果是等待最久的線程,則返回false;
     	 // 並調用compareAndSetState方法獲取到鎖
         if (!hasQueuedPredecessors() &&
             compareAndSetState(0, acquires)) {
             // 設置當前執行線程爲當前線程current,目的是爲了後續鎖的可重入使用
             setExclusiveOwnerThread(current);
             return true;
         }
     }
     // 如果線程同步狀態state不爲0,且當前線程current等於當前執行線程(獨佔模式同步的當前所有者)。
     else if (current == getExclusiveOwnerThread()) {
         int nextc = c + acquires;
         if (nextc < 0)
             throw new Error("Maximum lock count exceeded");
         setState(nextc); // 對同步狀態state賦新值,
         // 由此可見ReentrantLock是一個可重入鎖,但是重入多少次就得unlock多少次,保證最終的同步狀態state爲0
         return true;
     }
     return false;
 }

  公平鎖中的hasQueuedPredecessors()方法,查詢是否有任何線程在等待獲取比當前線程更長的時間。

public final boolean hasQueuedPredecessors() {
     // The correctness of this depends on head being initialized
     // before tail and on head.next being accurate if the current
     // thread is first in queue.
     Node t = tail; // Read fields in reverse initialization order
     Node h = head;
     Node s;
     return h != t &&
         ((s = h.next) == null || s.thread != Thread.currentThread());
 }

  AQS中的addWaiter(Node mode)方法,如下所示:

private Node addWaiter(Node mode) {
	// 創建一個節點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)) { // 通過CAS操作設置尾結點
            pred.next = node; // 之前的尾結點,把新加入的節點設置爲它的下一個節點
            return node;
        }
    }
    enq(node);
    return node;
}

  將當前線程包裝成 Node 後,當前隊列不爲空的情況下,先嚐試把當前節點加入 隊列併成爲尾節點,如果不成功或者隊列爲空進入 enq(final Node node)方法。

private Node enq(final Node node) {
    for (;;) {
        Node t = tail; // 尾結點
        // 如果爲節點爲空,初始化一個節點
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
        	// 把當前節點加入隊列
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

  在 enq(final Node node)方法中,同步器通過“死循環”來保證節點的正確添 加,這個死循環中,做了兩件事,第一件,如果隊列爲空,初始化隊列,new 出 一個空節點,並讓首節點(head)和尾節點(tail)兩個引用都指向這個空節點; 第二件事,把當前節點加入隊列。 在“死循環”中只有通過 CAS 將節點設置成爲尾節點之後,當前線程才能從該方法返回,否則當前線程不斷地嘗試設置。
  節點進入同步隊列之後,再看看acquireQueued(final Node node, int arg)方法,如下所示:

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
        	// 獲取node的前驅結點,如果爲空拋出異常
            final Node p = node.predecessor();
            // 如果node節點的上一節點等於頭結點,且成功的獲取了同步狀態
            if (p == head && tryAcquire(arg)) {
                setHead(node); // 設置node爲頭節點
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 線程進入阻塞狀態,等待被喚醒
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

  其實就是一個自旋的過程,每個節點(或者說每個線程)都在自省地觀察, 當條件滿足,獲取到了同步狀態,就可以從這個自旋過程中退出,否則依舊留在 這個自旋過程中(並會阻塞節點的線程)。
  在 acquireQueued(final Node node,int arg)方法中,當前線程在“死循環”中 嘗試獲取同步狀態,而只有前驅節點是頭節點才能夠嘗試獲取同步狀態,這是爲 什麼?原因有兩個。
  第一,頭節點是成功獲取到同步狀態的節點,而頭節點的線程釋放了同步狀 態之後,將會喚醒其後繼節點,後繼節點的線程被喚醒後需要檢查自己的前驅節 點是否是頭節點。
  第二,維護同步隊列的 FIFO 原則。
  當前線程獲取到同步狀態後,讓首節點(head)這個引用指向自己所在節點。 當同步狀態獲取成功後,當前線程就從 acquire 方法返回了。如果同步器實現的 是鎖,那就代表當前線程獲得了鎖。

5.1.2 unlock()方法

  unlock()方法會調用AQS中的release(int arg)方法,以獨佔模式發佈。 如果調用 tryRelease(int arg)方法返回true,則通過解鎖一個或多個線程來實現。

public final boolean release(int arg) {
	// 同步狀態state-1,返回true
    if (tryRelease(arg)) {
    	// 獲取頭節點信息
        Node h = head;
        // 頭節點不爲空,waitStatus 不爲 0
        if (h != null && h.waitStatus != 0)
        	// 喚醒頭節點的下一節點
            unparkSuccessor(h);
        return true;
    }
    return false;
}

  調用 tryRelease(int releases)方法嘗試釋放鎖,如果返回爲空,則釋放鎖成功。

protected final boolean tryRelease(int releases) {
// 同步狀態state-1
    int c = getState() - releases;
    //如果當前線程不等於執行器中的線程,拋出異常處理。
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 同步狀態state爲0,表示釋放鎖成功。
    if (c == 0) {
        free = true;
        // 當前執行器中的線程設置爲空
        setExclusiveOwnerThread(null);
    }
    // 設置同步狀態
    setState(c);
    return free;
}

  執行release(int arg)該方法執行時,會喚醒首節點(head)所指向節點的後繼節點線程, unparkSuccessor(Node node)方法使用 LockSupport 來喚醒處於等待狀態的線程。

private void unparkSuccessor(Node node) { 
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    // 獲取下一節點
    Node s = node.next;
    // 如果節點爲null 或者節點處於被cancel狀態,先從尾部開始遍歷,找到需要被喚醒的節點線程
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);  // 喚醒節點
}

  這段代碼的意思,一般情況下,被喚醒的是 head 指向節點的後繼節點線程, 如果這個後繼節點處於被 cancel 狀態,(我推測開發者的思路這樣的:後繼節點 處於被 cancel 狀態,意味着當鎖競爭激烈時,隊列的第一個節點等了很久(一直 被還未加入隊列的節點搶走鎖),包括後續的節點 cancel 的機率都比較大,所以) 先從尾開始遍歷,找到最前面且沒有被 cancel 的節點。

5.2 非公平鎖 NonfairSync

5.2.1 lock()方法

  NonfairSync類中的lock()方法,首先通過CAS操作看是否能拿到鎖,如果拿不到則調用AQS中的acquire(int arg)方法。

final void lock() {
    // CAS操作,設置同步狀態,如果當前狀態等於期望值,則設置成功。線程成功獲取到鎖。
    if (compareAndSetState(0, 1))
        // 設置當前擁有獨佔訪問的線程(也就是設置當前執行線程爲當前獲取鎖的線程)
        setExclusiveOwnerThread(Thread.currentThread());
    else
        // 如果獲取鎖失敗,調用AQS中的acquire(int arg)方法
        acquire(1);
}

  調用ASQ中的 acquire(int arg)嘗試獲取鎖,如果獲取鎖失敗,則將當前線程的執行信息加入到AQS內部隊列中。

public final void acquire(int arg) {
   // 嘗試獲取鎖,如果獲取鎖失敗,則將當前線程執行信息放入到AQS內部的隊列中
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        // 中斷線程
        selfInterrupt();
}

  NonfairSync類重寫tryAcquire(int acquires)方法,調用非公平鎖的實現方法,嘗試獲取鎖。

protected final boolean tryAcquire(int acquires) {
    // 調用非公平鎖的實現方法,嘗試獲取鎖。
    return nonfairTryAcquire(acquires);
}

  調用Sync類的 nonfairTryAcquire(int acquires)方法,嘗試獲取鎖。

final boolean nonfairTryAcquire(int acquires) {
    // 獲取當前線程
    final Thread current = Thread.currentThread();
    // 獲取同步狀態state
    int c = getState();
    // 如果同步狀態等於0
    if (c == 0) {
        // CAS操作,設置同步狀態,如果當前狀態等於期望值,則設置成功。線程成功獲取到鎖。
        if (compareAndSetState(0, acquires)) {
            // 設置當前擁有獨佔訪問的線程(也就是設置當前執行線程爲當前獲取鎖的線程)
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 同步狀態不等於0 ,當前線程current等於當前執行器中的線程
    else if (current == getExclusiveOwnerThread()) {
        // 設置同步狀態state,由此可看出ReentrantLock是一個可重入鎖,但特別注意的是,重入多少次就得unlock多少次,保證最終state爲0
        int nextc = c + acquires;
        if (nextc < 0) // overflow
        throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

5.2.2 unlock()方法

  跟公平鎖一致,在此不多贅述。


備註:博主微信公衆號,不定期更新文章,歡迎掃碼關注。
在這裏插入圖片描述

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