1. 程式人生 > >魔鬼在細節,理解Java併發底層之AQS實現

魔鬼在細節,理解Java併發底層之AQS實現

jdk的JUC包(java.util.concurrent)提供大量Java併發工具提供使用,基本由Doug Lea編寫,很多地方值得學習和借鑑,是進階升級必經之路

本文從JUC包中常用的物件鎖、併發工具的使用和功能特性入手,帶著問題,由淺到深,一步步剖析併發底層AQS抽象類具體實現

名詞解釋

1 AQS

AQS是一個抽象類,類全路徑java.util.concurrent.locks.AbstractQueuedSynchronizer,抽象佇列同步器,是基於模板模式開發的併發工具抽象類,有如下併發類基於AQS實現:

2 CAS

CAS是Conmpare And Swap(比較和交換)的縮寫,是一個原子操作指令

CAS機制當中使用了3個基本運算元:記憶體地址addr,預期舊的值oldVal,要修改的新值newVal
更新一個變數的時候,只有當變數的預期值oldVal和記憶體地址addr當中的實際值相同時,才會將記憶體地址addr對應的值修改為newVal

基於樂觀鎖的思路,通過CAS再不斷嘗試和比較,可以對變數值執行緒安全地更新

3 執行緒中斷

執行緒中斷是一種執行緒協作機制,用於協作其他執行緒中斷任務的執行

當執行緒處於阻塞等待狀態,例如呼叫了wait()、join()、sleep()方法之後,呼叫執行緒的interrupt()方法之後,執行緒會馬上退出阻塞並收到InterruptedException;

當執行緒處於執行狀態,呼叫執行緒的interrupt()方法之後,執行緒並不會馬上中斷執行,需要線上程的具體任務執行邏輯中通過呼叫isInterrupted() 方法檢測執行緒中斷標誌位,然後主動響應中斷,通常是丟擲InterruptedException

物件鎖特性

下面先介紹物件鎖、併發工具有哪些基本特性,後面再逐步展開這些特性如何實現

1 顯式獲取

以ReentrantLock鎖為例,主要支援以下4種方式顯式獲取鎖

  • (1) 阻塞等待獲取
ReentrantLock lock = new ReentrantLock();
// 一直阻塞等待,直到獲取成功
lock.lock();
  • (2) 無阻塞嘗試獲取
ReentrantLock lock = new ReentrantLock();
// 嘗試獲取鎖,如果鎖已被其他執行緒佔用,則不阻塞等待直接返回false
// 返回true - 鎖是空閒的且被本執行緒獲取,或者已經被本執行緒持有
// 返回false - 獲取鎖失敗
boolean isGetLock = lock.tryLock();
  • (3) 指定時間內阻塞等待獲取
ReentrantLock lock = new ReentrantLock();
try {
    // 嘗試在指定時間內獲取鎖
    // 返回true - 鎖是空閒的且被本執行緒獲取,或者已經被本執行緒持有
    // 返回false - 指定時間內未獲取到鎖
    lock.tryLock(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
    // 內部呼叫isInterrupted() 方法檢測執行緒中斷標誌位,主動響應中斷
    e.printStackTrace();
}
  • (4) 響應中斷獲取
ReentrantLock lock = new ReentrantLock();
try {
    // 響應中斷獲取鎖
    // 如果呼叫執行緒的thread.interrupt()方法設定執行緒中斷,執行緒退出阻塞等待並丟擲中斷異常
    lock.lockInterruptibly();
} catch (InterruptedException e) {
    e.printStackTrace();
}

2 顯式釋放

ReentrantLock lock = new ReentrantLock();
lock.lock();
// ... 各種業務操作
// 顯式釋放鎖
lock.unlock();

3 可重入

已經獲取到鎖的執行緒,再次請求該鎖可以直接獲得

4 可共享

指同一個資源允許多個執行緒共享,例如讀寫鎖的讀鎖允許多個執行緒共享,共享鎖可以讓多個執行緒併發安全地訪問資料,提高程式執效率

5 公平、非公平

公平鎖:多個執行緒採用先到先得的公平方式競爭鎖。每次加鎖前都會檢查等待佇列裡面有沒有執行緒排隊,沒有才會嘗試獲取鎖。
非公平鎖:當一個執行緒採用非公平的方式獲取鎖時,該執行緒會首先去嘗試獲取鎖而不是等待。如果沒有獲取成功,才會進入等待佇列

因為非公平鎖方式可以使後來的執行緒有一定機率直接獲取鎖,減少了執行緒掛起等待的機率,效能優於公平鎖

AQS實現原理

1 基本概念

(1) Condition介面

類似Object的wait()、wait(long timeout)、notify()以及notifyAll()的方法結合synchronized內建鎖可以實現可以實現等待/通知模式,實現Lock介面的ReentrantLock、ReentrantReadWriteLock等物件鎖也有類似功能:

Condition介面定義了await()、awaitNanos(long)、signal()、signalAll()等方法,配合物件鎖例項實現等待/通知功能,原理是基於AQS內部類ConditionObject實現Condition介面,執行緒await後阻塞並進入CLH佇列(下面提到),等待其他執行緒呼叫signal方法後被喚醒

(2) CLH佇列

CLH佇列,CLH是演算法提出者Craig, Landin, Hagersten的名字簡稱

AQS內部維護著一個雙向FIFO的CLH佇列,AQS依賴它來管理等待中的執行緒,如果執行緒獲取同步競爭資源失敗時,會將執行緒阻塞,並加入到CLH同步佇列;當競爭資源空閒時,基於CLH佇列阻塞執行緒並分配資源

CLH的head節點儲存當前佔用資源的執行緒,或者是沒有執行緒資訊,其他節點儲存排隊執行緒資訊

CLH中每一個節點的狀態(waitStatus)取值如下:

  • CANCELLED(1):表示當前節點已取消排程。當timeout或被中斷(響應中斷的情況下),會觸發變更為此狀態,進入該狀態後的節點將不會再變化
  • SIGNAL(-1):表示後繼節點在等待當前節點喚醒。後繼節點入隊後進入休眠狀態之前,會將前驅節點的狀態更新為SIGNAL
  • CONDITION(-2):表示節點等待在Condition上,當其他執行緒呼叫了Condition的signal()方法後,CONDITION狀態的節點將從等待佇列轉移到同步佇列中,等待獲取同步鎖
  • PROPAGATE(-3):共享模式下,前驅節點不僅會喚醒其後繼節點,同時也可能會喚醒後繼的後繼節點
  • 0:新節點入隊時的預設狀態

(3) 資源共享方式

AQS定義兩種資源共享方式:
Exclusive 獨佔,只有一個執行緒能執行,如ReentrantLock
Share 共享,多個執行緒可同時執行,如Semaphore/CountDownLatch

(4) 阻塞/喚醒執行緒的方式

AQS 基於sun.misc.Unsafe類提供的park方法阻塞執行緒,unpark方法喚醒執行緒,被park方法阻塞的執行緒能響應interrupt()中斷請求退出阻塞

2 基本設計

核心設計思路:AQS提供一個框架,用於實現依賴於CLH佇列的阻塞鎖和相關的併發同步器。子類通過實現判定是否能獲取/釋放資源的protect方法,AQS基於這些protect方法實現對執行緒的排隊、喚醒的執行緒排程策略

AQS還提供一個支援執行緒安全原子更新的int型別變數作為同步狀態值(state),子類可以根據實際需求,靈活定義該變數代表的意義進行更新

通過子類重新定義的系列protect方法如下:

  • boolean tryAcquire(int) 獨佔方式嘗試獲取資源,成功則返回true,失敗則返回false
  • boolean tryRelease(int) 獨佔方式嘗試釋放資源,成功則返回true,失敗則返回false
  • int tryAcquireShared(int) 共享方式嘗試獲取資源。負數表示失敗;0表示成功,但沒有剩餘可用資源;正數表示成功,且有剩餘資源
  • boolean tryReleaseShared(int) 共享方式嘗試釋放資源,如果釋放後允許喚醒後續等待節點返回true,否則返回false

這些方法始終由需要需要排程協作的執行緒來呼叫,子類須以非阻塞的方式重新定義這些方法

AQS基於上述tryXXX方法,對外提供下列方法來獲取/釋放資源:

  • void acquire(int) 獨佔方式獲取到資源,執行緒直接返回,否則進入等待佇列,直到獲取到資源為止,且整個過程忽略中斷的影響
  • boolean release(int) 獨佔方式下執行緒釋放資源,先釋放指定量的資源,如果徹底釋放了(即state=0),它會喚醒等待佇列裡的其他執行緒來獲取資源
  • void acquireShared(int) 獨佔方式獲取資源
  • boolean releaseShared(int) 共享方式釋放資源

以獨佔模式為例:獲取/釋放資源的核心的實現如下:

 Acquire:
     while (!tryAcquire(arg)) {
        如果執行緒尚未排隊,則將其加入佇列;
     }

 Release:
     if (tryRelease(arg))
        喚醒CLH中第一個排隊執行緒

到這裡,有點繞,下面一張圖把上面介紹到的設計思路再重新捋一捋:

特性實現

下面介紹基於AQS的物件鎖、併發工具的一系列功能特性的實現原理

1 顯式獲取

該特性還是以ReentrantLock鎖為例,ReentrantLock是可重入物件鎖,執行緒每次請求獲取成功一次鎖,同步狀態值state加1,釋放鎖state減1,state為0代表沒有任何執行緒持有鎖

ReentrantLock鎖支援公平/非公平特性,下面的顯式獲取特性以公平鎖為例

(1) 阻塞等待獲取

基本實現如下:

  • 1、ReentrantLock實現AQS的tryAcquire(int)方法,先判斷:如果沒有任何執行緒持有鎖,或者當前執行緒已經持有鎖,則返回true,否則返回false
  • 2、AQS的acquire(int)方法判斷當前節點是否為head且基於tryAcquire(int)能否獲得資源,如果不能獲得,則加入CLH佇列排隊阻塞等待
  • 3、ReentrantLock的lock()方法基於AQS的acquire(int)方法阻塞等待獲取鎖

ReentrantLock中的tryAcquire(int)方法實現:

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 沒有任何執行緒持有鎖
    if (c == 0) {
        // 通過CLH佇列的head判斷沒有別的執行緒在比當前更早acquires
        // 且基於CAS設定state成功(期望的state舊值為0)
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            // 設定持有鎖的執行緒為當前執行緒
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 持有鎖的執行緒為當前執行緒
    else if (current == getExclusiveOwnerThread()) {
        // 僅僅在當前執行緒,單執行緒,不用基於CAS更新
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    // 其他執行緒已經持有鎖
    return false;
}

AQS的acquire(int)方法實現

public final void acquire(int arg) {
        // tryAcquire檢查釋放能獲取成功
        // addWaiter 構建CLH的節點物件併入隊
        // acquireQueued執行緒阻塞等待
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        // acquireQueued返回true,代表執行緒在獲取資源的過程中被中斷
        // 則呼叫該方法將執行緒中斷標誌位設定為true
        selfInterrupt();
}


final boolean acquireQueued(final Node node, int arg) {
    // 標記是否成功拿到資源
    boolean failed = true;
    try {
        // 標記等待過程中是否被中斷過
        boolean interrupted = false;
        // 迴圈直到資源釋放
        for (;;) {
            // 拿到前驅節點
            final Node p = node.predecessor();
            
            // 如果前驅是head,即本節點是第二個節點,才有資格去嘗試獲取資源
            // 可能是head釋放完資源喚醒本節點,也可能被interrupt()
            if (p == head && tryAcquire(arg)) {
                // 成功獲取資源
                setHead(node);
                // help GC
                p.next = null; 
                failed = false;
                return interrupted;
            }
            
            // 需要排隊阻塞等待
            // 如果在過程中執行緒中斷,不響應中斷
            // 且繼續排隊獲取資源,設定interrupted變數為true
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

(2) 無阻塞嘗試獲取

ReentrantLock中的tryLock()的實現僅僅是非公平鎖實現,實現邏輯基本與tryAcquire一致,不同的是沒有通過hasQueuedPredecessors()檢查CLH佇列的head是否有其他執行緒在等待,這樣當資源釋放時,有執行緒請求資源能插隊優先獲取

ReentrantLock中tryLock()具體實現如下:

public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 沒有任何執行緒持有鎖
    if (c == 0) {
        // 基於CAS設定state成功(期望的state舊值為0)
        // 沒有檢查CLH佇列中是否有執行緒在等待
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 持有鎖的執行緒為當前執行緒
    else if (current == getExclusiveOwnerThread()) {
        // 僅僅在當前執行緒,單執行緒,不用基於CAS更新
        int nextc = c + acquires;
        if (nextc < 0) // overflow,整數溢位
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    // 其他執行緒已經持有鎖
    return false;
}

(3) 指定時間內阻塞等待獲取

基本實現如下:

  • 1、ReentrantLock的tryLock(long, TimeUnit)呼叫AQS的tryAcquireNanos(int, long)方法
  • 2、AQS的tryAcquireNanos先呼叫tryAcquire(int)嘗試獲取,獲取不到再呼叫doAcquireNanos(int, long)方法
  • 3、AQS的doAcquireNanos判斷當前節點是否為head且基於tryAcquire(int)能否獲得資源,如果不能獲得且超時時間大於1微秒,則休眠一段時間後再嘗試獲取

ReentrantLock中的實現如下:

public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    // 如果執行緒已經被interrupt()方法設定中斷 
    if (Thread.interrupted())
        throw new InterruptedException();
    // 先tryAcquire嘗試獲取鎖 
    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();
            // 如果前驅是head,即本節點是第二個節點,才有資格去嘗試獲取資源
            // 可能是head釋放完資源喚醒本節點,也可能被interrupt()
            if (p == head && tryAcquire(arg)) {
                // 成功獲取資源
                setHead(node);
                // help GC
                p.next = null; 
                failed = false;
                return true;
            }
            // 更新剩餘超時時間
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L)
                return false;
            // 排隊是否需要排隊阻塞等待 
            // 且超時時間大於1微秒,則執行緒休眠到超時時間到了再嘗試獲取
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);

            // 如果執行緒已經被interrupt()方法設定中斷
            // 則不再排隊,直接退出   
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

(4) 響應中斷獲取

ReentrantLock響應中斷獲取鎖的方式是:當執行緒在park方法休眠中響應thead.interrupt()方法中斷喚醒時,檢查到執行緒中斷標誌位為true,主動丟擲異常,核心實現在AQS的doAcquireInterruptibly(int)方法中

基本實現與阻塞等待獲取類似,只是呼叫從AQS的acquire(int)方法,改為呼叫AQS的doAcquireInterruptibly(int)方法

private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    // 標記是否成功拿到資源
    boolean failed = true;
    try {
        for (;;) {
            // 拿到前驅節點
            final Node p = node.predecessor();
            
            // 如果前驅是head,即本節點是第二個節點,才有資格去嘗試獲取資源
            // 可能是head釋放完資源喚醒本節點,也可能被interrupt()
            if (p == head && tryAcquire(arg)) {
                // 成功獲取資源
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            
            // 需要排隊阻塞等待
            if (shouldParkAfterFailedAcquire(p, node) &&
                // 從排隊阻塞中喚醒,如果檢查到中斷標誌位為true
                parkAndCheckInterrupt())
                // 主動響應中斷
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

2 顯式釋放

AQS資源共享方式分為獨佔式和共享式,這裡先以ReentrantLock為例介紹獨佔式資源的顯式釋放,共享式後面會介紹到

與顯式獲取有類似之處,ReentrantLock顯式釋放基本實現如下:

  • 1、ReentrantLock實現AQS的tryRelease(int)方法,方法將state變數減1,如果state變成0代表沒有任何執行緒持有鎖,返回true,否則返回false
  • 2、AQS的release(int)方法基於tryRelease(int)排隊是否有任何執行緒持有資源,如果沒有,則喚醒CLH佇列中頭節點的執行緒
  • 3、被喚醒後的執行緒繼續執行acquireQueued(Node,int)或者doAcquireNanos(int, long)或者doAcquireInterruptibly(int)中for(;;)中的邏輯,繼續嘗試獲取資源

ReentrantLock中tryRelease(int)方法實現如下:

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    // 只有持有鎖的執行緒才有資格釋放鎖
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
        
    // 標識是否沒有任何執行緒持有鎖    
    boolean free = false;
    
    // 沒有任何執行緒持有鎖
    // 可重入鎖每lock一次都需要對應一次unlock
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

AQS中的release(int)方法實現如下:

public final boolean release(int arg) {
    // 嘗試釋放資源
    if (tryRelease(arg)) {
        Node h = head;
        // 頭節點不為空
        // 後繼節點入隊後進入休眠狀態之前,會將前驅節點的狀態更新為SIGNAL(-1)
        // 頭節點狀態為0,代表沒有後繼的等待節點
        if (h != null && h.waitStatus != 0)
            // 喚醒第二個節點
            // 頭節點是佔用資源的執行緒,第二個節點才是首個等待資源的執行緒
            unparkSuccessor(h);
        return true;
    }
    return false;
}

3 可重入

可重入的實現比較簡單,以ReentrantLock為例,主要是在tryAcquire(int)方法中實現,持有鎖的執行緒是不是當前執行緒,如果是,更新同步狀態值state,並返回true,代表能獲取鎖

4 可共享

可共享資源以ReentrantReadWriteLock為例,跟獨佔鎖ReentrantLock的區別主要在於,獲取的時候,多個執行緒允許共享讀鎖,當寫鎖釋放時,多個阻塞等待讀鎖的執行緒能同時獲取到

ReentrantReadWriteLock類中將AQS的state同步狀態值定義為,高16位為讀鎖持有數,低16位為寫鎖持有鎖

ReentrantReadWriteLock中tryAcquireShared(int)、tryReleaseShared(int)實現的邏輯較長,主要涉及讀寫互斥、可重入判斷、讀鎖對寫鎖的讓步,篇幅所限,這裡就不展開了

獲取讀鎖(ReadLock.lock())主要實現如下:

  • 1、ReentrantReadWriteLock實現AQS的tryAcquireShared(int)方法,判斷當前執行緒能否獲得讀鎖
  • 2、AQS的acquireShared(int)先基於tryAcquireShared(int)嘗試獲取資源,如果獲取失敗,則加入CLH佇列排隊阻塞等待
  • 3、ReentrantReadWriteLock的ReadLock.lock()方法基於AQS的acquireShared(int)方法阻塞等待獲取鎖

AQS中共享模式獲取資源的具體實現如下:

public final void acquireShared(int arg) {
    // tryAcquireShared返回負數代表獲取共享資源失敗
    // 則通過進入等待佇列,直到獲取到資源為止才返回
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

// 與前面介紹到的acquireQueued邏輯基本一致
// 不同的是將tryAcquire改為tryAcquireShared
// 還有資源獲取成功後將傳播給CLH佇列上等待該資源的節點
private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);
     // 標記是否成功拿到資源
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                // 資源獲取成功
                if (r >= 0) {
                    // 傳播給CLH佇列上等待該資源的節點                             
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            // 需要排隊阻塞等待
            // 如果在過程中執行緒中斷,不響應中斷
            // 且繼續排隊獲取資源,設定interrupted變數為true
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

//  資源傳播給CLH佇列上等待該資源的節點 
private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; 
    setHead(node);
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        if (s == null || s.isShared())
            // 釋放共享資源
            doReleaseShared();
    }
}

釋放讀鎖(ReadLock.unlock())主要實現如下:
ReentrantReadWriteLock中共享資源的釋放主要實現如下:

  • 1、ReentrantReadWriteLock實現AQS的tryReleaseShared(int)方法,判斷讀鎖釋放後是否還有執行緒持有讀鎖
  • 2、AQS的releaseShared(int)基於tryReleaseShared(int)判斷是否需要CLH佇列中的休眠執行緒,如果需要就執行doReleaseShared()
  • 3、ReentrantReadWriteLock的ReadLock.unlock()方法基於AQS的releaseShared(int)方法釋放鎖

AQS中共享模式釋放資源具體實現如下:

public final boolean releaseShared(int arg) {
    // 允許喚醒CLH中的休眠執行緒
    if (tryReleaseShared(arg)) {
        // 執行資源釋放
        doReleaseShared();
        return true;
    }
    return false;
}
    
private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            // 當前節點正在等待資源
            if (ws == Node.SIGNAL) {
                // 當前節點被其他執行緒喚醒了
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            
                unparkSuccessor(h);
            }
            // 進入else的條件是,當前節點剛剛成為頭節點
            // 尾節點剛剛加入CLH佇列,還沒在休眠前將前驅節點狀態改為SIGNAL
            // CAS失敗是尾節點已經在休眠前將前驅節點狀態改為SIGNAL
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;               
        }
        // 每次喚醒後驅節點後,執行緒進入doAcquireShared方法,然後更新head
        // 如果h變數在本輪迴圈中沒有被改變,說明head == tail,佇列中節點全部被喚醒
        if (h == head)                 
            break;
    }
}

5 公平、非公平

這個特性實現比較簡單,以ReentrantLock鎖為例,公平鎖直接基於AQS的acquire(int)獲取資源,而非公平鎖先嚐試插隊:基於CAS,期望state同步變數值為0(沒有任何執行緒持有鎖),更新為1,如果全部CAS更新失敗再進行排隊

// 公平鎖實現
final void lock() {
    acquire(1);
}

// 非公平鎖實現
final void lock() {
    // 第1次CAS
    // state值為0代表沒有任何執行緒持有鎖,直接插隊獲得鎖
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

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

// 在nonfairTryAcquire方法中再次CAS嘗試獲取鎖
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 第2次CAS嘗試獲取鎖
        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;
}

總結

AQS的state變數值的含義不一定代表資源,不同的AQS的繼承類可以對state變數值有不同的定義

例如在countDownLatch類中,state變數值代表還需釋放的latch計數(可以理解為需要開啟的門閂數),需要每個門閂都開啟,門才能開啟,所有等待執行緒才會開始執行,每次countDown()就會對state變數減1,如果state變數減為0,則喚醒CLH佇列中的休眠執行緒

學習類似底層原始碼建議先定幾個問題,帶著問題學習;通俗學習前建議先理解透徹整體設計,整體原理(可以先閱讀相關文件資料),再研究和原始碼細節,避免一開始就扎進去原始碼,容易無功而返

相關推薦

魔鬼細節理解Java併發底層AQS實現

jdk的JUC包(java.util.concurrent)提供大量Java併發工具提供使用,基本由Doug Lea編寫,很多地方值得學習和借鑑,是進階升級必經之路 本文從JUC包中常用的物件鎖、併發工具的使用和功能特性入手,帶著問題,由淺到深,一步步剖析併發底層AQS抽象類具體實現 名詞解釋 1 AQS

深入MyBatis原始碼理解Java設計模式介面卡模式

什麼是介面卡模式 定義:將一個系統的介面轉換成另外一種形式,從而使原來不能直接呼叫的介面變得可以呼叫。 介面卡模

Java併發程式設計AQS

一、什麼是AQS   AQS(AbstractQueuedSynchronize:佇列同步器)是用來構建鎖或者其他同步元件的基礎框架,很多同步類都是在它的基礎上實現的,比如常用的ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore。 &

java併發程式設計happens-before原則先行發生原則

下文為自己學習筆記。 關鍵詞理解: JMM:java memory model java記憶體模型 int a=1;//A int b=3;//B int c=a*b;//C 在上邊這段程式碼中,有A\B\C個語句 C依賴於A、B兩個語句,所以Ahappens-before於C,

深入理解Java併發synchronized實現原理

關聯文章: 本篇主要是對Java併發中synchronized關鍵字進行較為深入的探索,這些知識點結合博主對synchronized的個人理解以及相關的書籍的講解(在結尾參考資料),如有誤處,歡迎留言。 執行緒安全是併發程式

Java併發程式設計CountDownLatchCyclicBarrier實現一組執行緒相互等待、喚醒

java多執行緒應用場景不少,有時自己編寫程式碼又不太容易實現,好在concurrent包提供了不少實現類,還有google的guava包更是提供了一些最佳實踐,這讓我們在面對一些多執行緒的場景時,有了不少的選擇。這裡主要是看幾個涉及到多執行緒等待的工具類。一 CountDo

深入理解 Java 併發 synchronized 實現原理

關聯文章深入理解Java型別資訊(Class物件)與反射機制深入理解Java列舉型別(enum)深入理解Java註解型別(@Annotation)深入理解Java併發之synchronized實現原理本篇主要是對Java併發中synchronized關鍵字進行較為深入的探索,這些知識點結合博主對synchro

深入理解 Java 虛擬機學習筆記(1)

over 信息 hotspot 體系 ima 模塊化 介紹 style 創建 本書結構: 從宏觀的角度介紹了整個Java技術體系、Java和JVM的發展歷程、模塊化,以及JDK的編譯 講解了JVM的自動內存管理,包括虛擬機內存區域的劃分原理以及各種內存溢出異常產

深入理解JAVA虛擬機JVM性能篇---基礎知識點

默認 生命周期 ima 線程 images 對象 情況 -- 是否 一、堆與棧   堆和棧是程序運行的關鍵,其間的關系有必要理清楚,兩者如下圖所示:      1. 堆:   所有線程共享,堆中只負責存儲對象信息。   2. 棧:   在Java中每個線程都會有一個相應的線

深入理解JAVA虛擬機JVM性能篇---垃圾回收

小數據 alt tro 調優 permsize 多次 快速 com src 一、基本垃圾回收算法 1. 按基本回收策略分   1) 引用計數(Reference Counting)     對象增加一個引用,即增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,只用收集計

深入理解Java虛擬機垃圾收集一

native 直觀 軟引用 老年 系統清理 邊界 lan 除了 每次 “生存還是死亡” 如何來判定對象是否存活?針對這個問題書中給出了兩種算法,分別是引用計數算法和可達性分析算法 引用計數算法 該算法的思路簡單並且易於實現。我們給對象中添加一個引用計數器,當有一個地方引用

java併發程式設計利用CAS保證操作的原子性

import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class Counter { private AtomicInteger at

Java併發程式設計CyclicBarrier

CyclicBarrier可以控制這樣的場景: 對多個執行緒,他們執行自己程式碼(執行run方法)的時間不一樣; 比如有3個執行緒,其run方法執行時間分別為1s, 2s, 3s。如果我們想在三個執行緒都完成自己的任務時執行相應的操作,CyclicBarrier就派上用場了。 寫了一

Java併發程式設計鎖機制LockSupport工具

關於文章涉及到的jdk原始碼,這裡把最新的jdk原始碼分享給大家----->jdk原始碼 前言 在上篇文章《Java併發程式設計之鎖機制之AQS(AbstractQueuedSynchronizer)》中我們瞭解了整個AQS的內部結構,與其獨佔式與共享式獲取同步狀態的實現

Java併發程式設計執行緒生命週期、守護執行緒、優先順序和join、sleep、yield

Java併發程式設計中,其中一個難點是對執行緒生命週期的理解,和多種執行緒控制方法、執行緒溝通方法的靈活運用。這些方法和概念之間彼此聯絡緊密,共同構成了Java併發程式設計基石之一。 Java執行緒的生命週期 Java執行緒類定義了New、Runnable、Running Man、Blocked和Dead

Java併發程式設計執行緒安全、執行緒通訊

Java多執行緒開發中最重要的一點就是執行緒安全的實現了。所謂Java執行緒安全,可以簡單理解為當多個執行緒訪問同一個共享資源時產生的資料不一致問題。為此,Java提供了一系列方法來解決執行緒安全問題。 synchronized synchronized用於同步多執行緒對共享資源的訪問,在實現中分為同步程

Java併發程式設計ThreadGroup

ThreadGroup是Java提供的一種對執行緒進行分組管理的手段,可以對所有執行緒以組為單位進行操作,如設定優先順序、守護執行緒等。 執行緒組也有父子的概念,如下圖: 執行緒組的建立 1 public class ThreadGroupCreator { 2 3 publi

Java併發程式設計Exchanger

概述   用於執行緒間資料的交換。它提供一個同步點,在這個同步點,兩個執行緒可以交換彼此的資料。這兩個執行緒通過exchange方法交換資料,如果第一個執行緒先執行exchange()方法,它會一直等待第二個執行緒也執行exchange方法,當兩個執行緒都到達同步點時,這兩個執行緒就可以交換資料

java併發程式設計使用 CountDownLatch 控制多個執行緒執行順序

有時候會有這樣的需求,多個執行緒同時工作,然後其中幾個可以隨意併發執行,但有一個執行緒需要等其他執行緒工作結束後,才能開始。舉個例子,開啟多個執行緒分塊下載一個大檔案,每個執行緒只下載固定的一截,最後由另外一個執行緒來拼接所有的分段,那麼這時候我們可以考慮使用CountDownLatch來控制併發。

基於JVM原理、JMM模型和CPU快取模型深入理解Java併發程式設計

許多以Java多執行緒開發為主題的技術書籍,都會把對Java虛擬機器和Java記憶體模型的講解,作為講授Java併發程式設計開發的主要內容,有的還深入到計算機系統的記憶體、CPU、快取等予以說明。實際上,在實際的Java開發工作中,僅僅瞭解併發程式設計的建立、啟動、管理和通訊等基本知識還是不夠的。一