魔鬼在細節(jié)若河,理解Java并發(fā)底層之AQS實現(xiàn)

jdk的JUC包(java.util.concurrent)提供大量Java并發(fā)工具提供使用平道,基本由Doug Lea編寫闯估,很多地方值得學習和借鑒灼舍,是進階升級必經(jīng)之路

本文從JUC包中常用的對象鎖、并發(fā)工具的使用和功能特性入手涨薪,帶著問題骑素,由淺到深,一步步剖析并發(fā)底層AQS抽象類具體實現(xiàn)

名詞解釋

1 AQS

AQS是一個抽象類刚夺,類全路徑java.util.concurrent.locks.AbstractQueuedSynchronizer献丑,抽象隊列同步器,是基于模板模式開發(fā)的并發(fā)工具抽象類侠姑,有如下并發(fā)類基于AQS實現(xiàn):

2 CAS

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

CAS機制當中使用了3個基本操作數(shù):內(nèi)存地址addr,預期舊的值oldVal莽红,要修改的新值newVal
更新一個變量的時候妥畏,只有當變量的預期值oldVal和內(nèi)存地址addr當中的實際值相同時,才會將內(nèi)存地址addr對應的值修改為newVal

基于樂觀鎖的思路安吁,通過CAS再不斷嘗試和比較醉蚁,可以對變量值線程安全地更新

3 線程中斷

線程中斷是一種線程協(xié)作機制,用于協(xié)作其他線程中斷任務的執(zhí)行

當線程處于阻塞等待狀態(tài)鬼店,例如調(diào)用了wait()网棍、join()、sleep()方法之后薪韩,調(diào)用線程的interrupt()方法之后确沸,線程會馬上退出阻塞并收到InterruptedException捌锭;

當線程處于運行狀態(tài)俘陷,調(diào)用線程的interrupt()方法之后,線程并不會馬上中斷執(zhí)行观谦,需要在線程的具體任務執(zhí)行邏輯中通過調(diào)用isInterrupted() 方法檢測線程中斷標志位拉盾,然后主動響應中斷,通常是拋出InterruptedException

對象鎖特性

下面先介紹對象鎖豁状、并發(fā)工具有哪些基本特性捉偏,后面再逐步展開這些特性如何實現(xiàn)

1 顯式獲取

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

  • (1) 阻塞等待獲取
ReentrantLock lock = new ReentrantLock();
// 一直阻塞等待泻红,直到獲取成功
lock.lock();
  • (2) 無阻塞嘗試獲取
ReentrantLock lock = new ReentrantLock();
// 嘗試獲取鎖夭禽,如果鎖已被其他線程占用,則不阻塞等待直接返回false
// 返回true - 鎖是空閑的且被本線程獲取谊路,或者已經(jīng)被本線程持有
// 返回false - 獲取鎖失敗
boolean isGetLock = lock.tryLock();
  • (3) 指定時間內(nèi)阻塞等待獲取
ReentrantLock lock = new ReentrantLock();
try {
    // 嘗試在指定時間內(nèi)獲取鎖
    // 返回true - 鎖是空閑的且被本線程獲取讹躯,或者已經(jīng)被本線程持有
    // 返回false - 指定時間內(nèi)未獲取到鎖
    lock.tryLock(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
    // 內(nèi)部調(diào)用isInterrupted() 方法檢測線程中斷標志位,主動響應中斷
    e.printStackTrace();
}
  • (4) 響應中斷獲取
ReentrantLock lock = new ReentrantLock();
try {
    // 響應中斷獲取鎖
    // 如果調(diào)用線程的thread.interrupt()方法設置線程中斷,線程退出阻塞等待并拋出中斷異常
    lock.lockInterruptibly();
} catch (InterruptedException e) {
    e.printStackTrace();
}

2 顯式釋放

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

3 可重入

已經(jīng)獲取到鎖的線程潮梯,再次請求該鎖可以直接獲得

4 可共享

指同一個資源允許多個線程共享骗灶,例如讀寫鎖的讀鎖允許多個線程共享,共享鎖可以讓多個線程并發(fā)安全地訪問數(shù)據(jù)秉馏,提高程序執(zhí)效率

5 公平耙旦、非公平

公平鎖:多個線程采用先到先得的公平方式競爭鎖。每次加鎖前都會檢查等待隊列里面有沒有線程排隊萝究,沒有才會嘗試獲取鎖免都。
非公平鎖:當一個線程采用非公平的方式獲取鎖時,該線程會首先去嘗試獲取鎖而不是等待帆竹。如果沒有獲取成功琴昆,才會進入等待隊列

因為非公平鎖方式可以使后來的線程有一定幾率直接獲取鎖,減少了線程掛起等待的幾率馆揉,性能優(yōu)于公平鎖

AQS實現(xiàn)原理

1 基本概念

(1) Condition接口

類似Object的wait()业舍、wait(long timeout)、notify()以及notifyAll()的方法結合synchronized內(nèi)置鎖可以實現(xiàn)可以實現(xiàn)等待/通知模式升酣,實現(xiàn)Lock接口的ReentrantLock舷暮、ReentrantReadWriteLock等對象鎖也有類似功能:

Condition接口定義了await()噩茄、awaitNanos(long)下面、signal()、signalAll()等方法绩聘,配合對象鎖實例實現(xiàn)等待/通知功能沥割,原理是基于AQS內(nèi)部類ConditionObject實現(xiàn)Condition接口,線程await后阻塞并進入CLH隊列(下面提到)凿菩,等待其他線程調(diào)用signal方法后被喚醒

(2) CLH隊列

CLH隊列机杜,CLH是算法提出者Craig, Landin, Hagersten的名字簡稱

AQS內(nèi)部維護著一個雙向FIFO的CLH隊列,AQS依賴它來管理等待中的線程衅谷,如果線程獲取同步競爭資源失敗時椒拗,會將線程阻塞,并加入到CLH同步隊列获黔;當競爭資源空閑時蚀苛,基于CLH隊列阻塞線程并分配資源

CLH的head節(jié)點保存當前占用資源的線程,或者是沒有線程信息玷氏,其他節(jié)點保存排隊線程信息

CLH

CLH中每一個節(jié)點的狀態(tài)(waitStatus)取值如下:

  • CANCELLED(1):表示當前節(jié)點已取消調(diào)度堵未。當timeout或被中斷(響應中斷的情況下),會觸發(fā)變更為此狀態(tài)盏触,進入該狀態(tài)后的節(jié)點將不會再變化
  • SIGNAL(-1):表示后繼節(jié)點在等待當前節(jié)點喚醒渗蟹。后繼節(jié)點入隊后進入休眠狀態(tài)之前侦厚,會將前驅節(jié)點的狀態(tài)更新為SIGNAL
  • CONDITION(-2):表示節(jié)點等待在Condition上,當其他線程調(diào)用了Condition的signal()方法后拙徽,CONDITION狀態(tài)的節(jié)點將從等待隊列轉移到同步隊列中刨沦,等待獲取同步鎖
  • PROPAGATE(-3):共享模式下,前驅節(jié)點不僅會喚醒其后繼節(jié)點膘怕,同時也可能會喚醒后繼的后繼節(jié)點
  • 0:新節(jié)點入隊時的默認狀態(tài)

(3) 資源共享方式

AQS定義兩種資源共享方式:
Exclusive 獨占想诅,只有一個線程能執(zhí)行,如ReentrantLock
Share 共享岛心,多個線程可同時執(zhí)行来破,如Semaphore/CountDownLatch

(4) 阻塞/喚醒線程的方式

AQS 基于sun.misc.Unsafe類提供的park方法阻塞線程,unpark方法喚醒線程忘古,被park方法阻塞的線程能響應interrupt()中斷請求退出阻塞

2 基本設計

核心設計思路:AQS提供一個框架徘禁,用于實現(xiàn)依賴于CLH隊列的阻塞鎖和相關的并發(fā)同步器。子類通過實現(xiàn)判定是否能獲取/釋放資源的protect方法髓堪,AQS基于這些protect方法實現(xiàn)對線程的排隊送朱、喚醒的線程調(diào)度策略

AQS還提供一個支持線程安全原子更新的int類型變量作為同步狀態(tài)值(state),子類可以根據(jù)實際需求干旁,靈活定義該變量代表的意義進行更新

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

  • boolean tryAcquire(int) 獨占方式嘗試獲取資源驶沼,成功則返回true,失敗則返回false
  • boolean tryRelease(int) 獨占方式嘗試釋放資源争群,成功則返回true回怜,失敗則返回false
  • int tryAcquireShared(int) 共享方式嘗試獲取資源。負數(shù)表示失敾槐 玉雾;0表示成功,但沒有剩余可用資源轻要;正數(shù)表示成功复旬,且有剩余資源
  • boolean tryReleaseShared(int) 共享方式嘗試釋放資源,如果釋放后允許喚醒后續(xù)等待節(jié)點返回true伦腐,否則返回false

這些方法始終由需要需要調(diào)度協(xié)作的線程來調(diào)用赢底,子類須以非阻塞的方式重新定義這些方法

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

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

以獨占模式為例:獲取/釋放資源的核心的實現(xiàn)如下:

 Acquire:
     while (!tryAcquire(arg)) {
        如果線程尚未排隊革半,則將其加入隊列碑定;
     }

 Release:
     if (tryRelease(arg))
        喚醒CLH中第一個排隊線程

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

AQS基本設計

特性實現(xiàn)

下面介紹基于AQS的對象鎖延刘、并發(fā)工具的一系列功能特性的實現(xiàn)原理

1 顯式獲取

該特性還是以ReentrantLock鎖為例,ReentrantLock是可重入對象鎖六敬,線程每次請求獲取成功一次鎖碘赖,同步狀態(tài)值state加1,釋放鎖state減1外构,state為0代表沒有任何線程持有鎖

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

(1) 阻塞等待獲取

基本實現(xiàn)如下:

  • 1、ReentrantLock實現(xiàn)AQS的tryAcquire(int)方法审编,先判斷:如果沒有任何線程持有鎖撼班,或者當前線程已經(jīng)持有鎖,則返回true垒酬,否則返回false
  • 2砰嘁、AQS的acquire(int)方法判斷當前節(jié)點是否為head且基于tryAcquire(int)能否獲得資源,如果不能獲得勘究,則加入CLH隊列排隊阻塞等待
  • 3般码、ReentrantLock的lock()方法基于AQS的acquire(int)方法阻塞等待獲取鎖

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

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;
    }
    // 其他線程已經(jīng)持有鎖
    return false;
}

AQS的acquire(int)方法實現(xiàn)

public final void acquire(int arg) {
        // tryAcquire檢查釋放能獲取成功
        // addWaiter 構建CLH的節(jié)點對象并入隊
        // acquireQueued線程阻塞等待
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        // acquireQueued返回true板祝,代表線程在獲取資源的過程中被中斷
        // 則調(diào)用該方法將線程中斷標志位設置為true
        selfInterrupt();
}


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

(2) 無阻塞嘗試獲取

ReentrantLock中的tryLock()的實現(xiàn)僅僅是非公平鎖實現(xiàn)橘洞,實現(xiàn)邏輯基本與tryAcquire一致,不同的是沒有通過hasQueuedPredecessors()檢查CLH隊列的head是否有其他線程在等待说搅,這樣當資源釋放時炸枣,有線程請求資源能插隊優(yōu)先獲取

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

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适肠,整數(shù)溢出
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    // 其他線程已經(jīng)持有鎖
    return false;
}

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

基本實現(xiàn)如下:

  • 1、ReentrantLock的tryLock(long, TimeUnit)調(diào)用AQS的tryAcquireNanos(int, long)方法
  • 2候引、AQS的tryAcquireNanos先調(diào)用tryAcquire(int)嘗試獲取侯养,獲取不到再調(diào)用doAcquireNanos(int, long)方法
  • 3、AQS的doAcquireNanos判斷當前節(jié)點是否為head且基于tryAcquire(int)能否獲得資源澄干,如果不能獲得且超時時間大于1微秒逛揩,則休眠一段時間后再嘗試獲取

ReentrantLock中的實現(xiàn)如下:

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 {
    // 如果線程已經(jīng)被interrupt()方法設置中斷 
    if (Thread.interrupted())
        throw new InterruptedException();
    // 先tryAcquire嘗試獲取鎖 
    return tryAcquire(arg) ||
        doAcquireNanos(arg, nanosTimeout);
}

AQS中的實現(xiàn)如下:

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 (;;) {
            // 拿到前驅節(jié)點
            final Node p = node.predecessor();
            // 如果前驅是head柠傍,即本節(jié)點是第二個節(jié)點,才有資格去嘗試獲取資源
            // 可能是head釋放完資源喚醒本節(jié)點辩稽,也可能被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);

            // 如果線程已經(jīng)被interrupt()方法設置中斷
            // 則不再排隊,直接退出   
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

(4) 響應中斷獲取

ReentrantLock響應中斷獲取鎖的方式是:當線程在park方法休眠中響應thead.interrupt()方法中斷喚醒時逞泄,檢查到線程中斷標志位為true徐紧,主動拋出異常,核心實現(xiàn)在AQS的doAcquireInterruptibly(int)方法中

基本實現(xiàn)與阻塞等待獲取類似炭懊,只是調(diào)用從AQS的acquire(int)方法并级,改為調(diào)用AQS的doAcquireInterruptibly(int)方法

private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.EXCLUSIVE);
    // 標記是否成功拿到資源
    boolean failed = true;
    try {
        for (;;) {
            // 拿到前驅節(jié)點
            final Node p = node.predecessor();
            
            // 如果前驅是head,即本節(jié)點是第二個節(jié)點侮腹,才有資格去嘗試獲取資源
            // 可能是head釋放完資源喚醒本節(jié)點嘲碧,也可能被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顯式釋放基本實現(xiàn)如下:

  • 1加矛、ReentrantLock實現(xiàn)AQS的tryRelease(int)方法履婉,方法將state變量減1,如果state變成0代表沒有任何線程持有鎖斟览,返回true毁腿,否則返回false
  • 2、AQS的release(int)方法基于tryRelease(int)排隊是否有任何線程持有資源苛茂,如果沒有已烤,則喚醒CLH隊列中頭節(jié)點的線程
  • 3、被喚醒后的線程繼續(xù)執(zhí)行acquireQueued(Node,int)或者doAcquireNanos(int, long)或者doAcquireInterruptibly(int)中for(;;)中的邏輯妓羊,繼續(xù)嘗試獲取資源

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

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)方法實現(xiàn)如下:

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

3 可重入

可重入的實現(xiàn)比較簡單裕循,以ReentrantLock為例,主要是在tryAcquire(int)方法中實現(xiàn)净刮,持有鎖的線程是不是當前線程剥哑,如果是,更新同步狀態(tài)值state庭瑰,并返回true星持,代表能獲取鎖

4 可共享

可共享資源以ReentrantReadWriteLock為例,跟獨占鎖ReentrantLock的區(qū)別主要在于弹灭,獲取的時候督暂,多個線程允許共享讀鎖,當寫鎖釋放時穷吮,多個阻塞等待讀鎖的線程能同時獲取到

ReentrantReadWriteLock類中將AQS的state同步狀態(tài)值定義為逻翁,高16位為讀鎖持有數(shù),低16位為寫鎖持有鎖

ReentrantReadWriteLock中tryAcquireShared(int)捡鱼、tryReleaseShared(int)實現(xiàn)的邏輯較長八回,主要涉及讀寫互斥、可重入判斷驾诈、讀鎖對寫鎖的讓步缠诅,篇幅所限,這里就不展開了

獲取讀鎖(ReadLock.lock())主要實現(xiàn)如下

  • 1乍迄、ReentrantReadWriteLock實現(xiàn)AQS的tryAcquireShared(int)方法管引,判斷當前線程能否獲得讀鎖
  • 2、AQS的acquireShared(int)先基于tryAcquireShared(int)嘗試獲取資源闯两,如果獲取失敗褥伴,則加入CLH隊列排隊阻塞等待
  • 3、ReentrantReadWriteLock的ReadLock.lock()方法基于AQS的acquireShared(int)方法阻塞等待獲取鎖

AQS共享模式獲取資源的具體實現(xiàn)如下:

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

// 與前面介紹到的acquireQueued邏輯基本一致
// 不同的是將tryAcquire改為tryAcquireShared
// 還有資源獲取成功后將傳播給CLH隊列上等待該資源的節(jié)點
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隊列上等待該資源的節(jié)點                             
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            // 需要排隊阻塞等待
            // 如果在過程中線程中斷重慢,不響應中斷
            // 且繼續(xù)排隊獲取資源,設置interrupted變量為true
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

//  資源傳播給CLH隊列上等待該資源的節(jié)點 
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())主要實現(xiàn)如下
ReentrantReadWriteLock共享資源的釋放主要實現(xiàn)如下:

  • 1逊躁、ReentrantReadWriteLock實現(xiàn)AQS的tryReleaseShared(int)方法似踱,判斷讀鎖釋放后是否還有線程持有讀鎖
  • 2、AQS的releaseShared(int)基于tryReleaseShared(int)判斷是否需要CLH隊列中的休眠線程稽煤,如果需要就執(zhí)行doReleaseShared()
  • 3屯援、ReentrantReadWriteLock的ReadLock.unlock()方法基于AQS的releaseShared(int)方法釋放鎖

AQS共享模式釋放資源具體實現(xiàn)如下:

public final boolean releaseShared(int arg) {
    // 允許喚醒CLH中的休眠線程
    if (tryReleaseShared(arg)) {
        // 執(zhí)行資源釋放
        doReleaseShared();
        return true;
    }
    return false;
}
    
private void doReleaseShared() {
    for (;;) {
        Node h = head;
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            // 當前節(jié)點正在等待資源
            if (ws == Node.SIGNAL) {
                // 當前節(jié)點被其他線程喚醒了
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            
                unparkSuccessor(h);
            }
            // 進入else的條件是,當前節(jié)點剛剛成為頭節(jié)點
            // 尾節(jié)點剛剛加入CLH隊列念脯,還沒在休眠前將前驅節(jié)點狀態(tài)改為SIGNAL
            // CAS失敗是尾節(jié)點已經(jīng)在休眠前將前驅節(jié)點狀態(tài)改為SIGNAL
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;               
        }
        // 每次喚醒后驅節(jié)點后狞洋,線程進入doAcquireShared方法,然后更新head
        // 如果h變量在本輪循環(huán)中沒有被改變绿店,說明head == tail吉懊,隊列中節(jié)點全部被喚醒
        if (h == head)                 
            break;
    }
}

5 公平、非公平

這個特性實現(xiàn)比較簡單假勿,以ReentrantLock鎖為例借嗽,公平鎖直接基于AQS的acquire(int)獲取資源,而非公平鎖先嘗試插隊:基于CAS转培,期望state同步變量值為0(沒有任何線程持有鎖)恶导,更新為1,如果全部CAS更新失敗再進行排隊

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

// 非公平鎖實現(xiàn)
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;
        }
    }
    // 已經(jīng)獲得鎖
    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計數(shù)(可以理解為需要打開的門閂數(shù)),需要每個門閂都打開裂垦,門才能打開顺囊,所有等待線程才會開始執(zhí)行,每次countDown()就會對state變量減1蕉拢,如果state變量減為0特碳,則喚醒CLH隊列中的休眠線程

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

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市恕汇,隨后出現(xiàn)的幾起案子腕唧,更是在濱河造成了極大的恐慌,老刑警劉巖瘾英,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件枣接,死亡現(xiàn)場離奇詭異,居然都是意外死亡缺谴,警方通過查閱死者的電腦和手機但惶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來湿蛔,“玉大人膀曾,你說我怎么就攤上這事⊙羯叮” “怎么了添谊?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長察迟。 經(jīng)常有香客問我斩狱,道長,這世上最難降的妖魔是什么扎瓶? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任所踊,我火速辦了婚禮,結果婚禮上概荷,老公的妹妹穿的比我還像新娘秕岛。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布继薛。 她就那樣靜靜地躺著修壕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪惋增。 梳的紋絲不亂的頭發(fā)上叠殷,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天改鲫,我揣著相機與錄音诈皿,去河邊找鬼。 笑死像棘,一個胖子當著我的面吹牛稽亏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播缕题,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼截歉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了烟零?” 一聲冷哼從身側響起瘪松,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎锨阿,沒想到半個月后宵睦,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡墅诡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年壳嚎,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片末早。...
    茶點故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡烟馅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出然磷,到底是詐尸還是另有隱情郑趁,我是刑警寧澤,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布姿搜,位于F島的核電站寡润,受9級特大地震影響,放射性物質發(fā)生泄漏痪欲。R本人自食惡果不足惜悦穿,卻給世界環(huán)境...
    茶點故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望业踢。 院中可真熱鬧栗柒,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至逛钻,卻和暖如春僚焦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背曙痘。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工芳悲, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人边坤。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓名扛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親茧痒。 傳聞我的和親對象是個殘疾皇子肮韧,可洞房花燭夜當晚...
    茶點故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容