Java AbstractQueuedSynchronizer(AQS)淺析之一

CSDN同步發(fā)布

本篇文章對(duì)Java中的AbstractQueuedSynchronizer(AQS)進(jìn)行分析和學(xué)習(xí)厚掷。若有不正之處請(qǐng)多多諒解唆阿,并歡迎批評(píng)指正。

為敘述方便锐墙,下文都以AQS替代AbstractQueuedSynchronizer析校。

使用的Java版本

src git:(master) ? java -version 
java version "1.8.0_201"
Java(TM) SE Runtime Environment (build 1.8.0_201-b09)
Java HotSpot(TM) 64-Bit Server VM (build 25.201-b09, mixed mode)

AQS是干什么的呢构罗?

下面是AQS類的部分介紹,咱也看不懂智玻,只能用百度翻譯一下哈哈遂唧,建議英文好的直接看源碼里的類注釋。

AQS提供一個(gè)框架吊奢,用于實(shí)現(xiàn)依賴先進(jìn)先出(FIFO)等待隊(duì)列的阻塞鎖和相關(guān)同步器(semaphores盖彭、events等)纹烹。對(duì)于大多數(shù)依賴單個(gè)原子{@code int}值來表示狀態(tài)的同步器來說,這個(gè)類是一個(gè)有用的基礎(chǔ)召边。子類必須定義改變這個(gè)狀態(tài)的受保護(hù)的方法铺呵,以及定義這個(gè)狀態(tài)在對(duì)象被獲取或釋放時(shí)的含義。鑒于這些隧熙,AQS類中的其他方法執(zhí)行所有排隊(duì)和阻塞機(jī)制片挂。

子類應(yīng)該被定義為非公共的內(nèi)部輔助類,用于實(shí)現(xiàn)其封閉類的同步屬性贞盯。AbstractQueuedSynchronizer類不實(shí)現(xiàn)任何同步接口音念。相反,它定義了例如{@link #acquireInterruptibly}這樣的方法躏敢,可以根據(jù)具體的鎖和相關(guān)的同步器來適當(dāng)?shù)卣{(diào)用闷愤,以實(shí)現(xiàn)它們的公共方法。

一句話:本篇文章只需要知道AQS可以用來實(shí)現(xiàn)鎖即可件余。

我們一般不會(huì)直接使用AQS讥脐,所以我們以ReentrantLock(可重入鎖)來引出AQS。明白了AQS就明白了ReentrantLock是如何獲取鎖以及釋放鎖的了啼器。

先說一下大致流程:

  • Java中的ReentrantLock的獲取鎖和釋放鎖是通過AQS來實(shí)現(xiàn)的旬渠。

  • AQS內(nèi)部維護(hù)了一個(gè)int類型的值來表示同步狀態(tài)和一個(gè)先進(jìn)先出(FIFO)的等待隊(duì)列

/**
 * 同步狀態(tài)
 */
private volatile int state;

/**
 * 等待隊(duì)列的head镀首,延遲初始化。除了初始化之外鼠次,head只能通過setHead方法來修改更哄。
 * 注意,如果head存在可以保證head的waitStatus不是CANCELLED.
 */
private transient volatile Node head;

/**
 * 等待隊(duì)列的尾腥寇,惰性初始成翩。只有在使用enq方法添加新的等待節(jié)點(diǎn)的時(shí)候修改。
 */
private transient volatile Node tail;

  • 對(duì)于非公平鎖赦役,線程總是會(huì)先嘗試獲取鎖麻敌,如果獲取成功就直接執(zhí)行,如果獲取失敗會(huì)進(jìn)入等待隊(duì)列掂摔。進(jìn)入等待隊(duì)列中的線程會(huì)休眠术羔,等待被喚醒。
  • 對(duì)于公平鎖乙漓,如果已經(jīng)有線程在等待獲取鎖了级历,那么新的線程就會(huì)直接排在等待隊(duì)列后面等待獲取鎖。
  • 持有鎖的線程執(zhí)行完畢釋放鎖叭披,喚醒等待隊(duì)列中的線程寥殖。
  • 線程被喚醒后會(huì)嘗試獲取鎖,如果成功獲取鎖那么線程就執(zhí)行,否則線程會(huì)再次休眠等待被喚醒嚼贡。

我們?cè)谑褂肦eentrantLock的過程中熏纯,既可以構(gòu)建一個(gè)使用非公平策略的ReentrantLock實(shí)例,也可以構(gòu)建一個(gè)使用公平策略的ReentrantLock實(shí)例粤策。

ReentrantLock的類結(jié)構(gòu)

public class ReentrantLock implements Lock, java.io.Serializable {

    //Sync成員變量
    private final Sync sync;
     
    //AQS的子類
    abstract static class Sync extends AbstractQueuedSynchronizer {
    
    }

    //非公平策略
    static final class NonfairSync extends Sync {

    }
    //公平策略
    static final class FairSync extends Sync {
    
    }

}

我們看到ReentrantLock類中有一個(gè)Sync類型的成員變量樟澜,Sync類繼承了AQS,然后
NonfairSync和FairSync都繼承了Sync掐场,分別實(shí)現(xiàn)非公平鎖和公平鎖往扔。

FairSync.png
NonfairSync.png

ReentrantLock的構(gòu)造函數(shù)

public ReentrantLock() {
   //使用非公平策略
   sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    //使用公平策略
    sync = fair ? new FairSync() : new NonfairSync();
}

我們可以選擇構(gòu)建公平的或非公平的ReentrantLock實(shí)例,ReentrantLock中獲取鎖和釋放鎖相關(guān)的方法如下所示熊户。我們先看非公平鎖的情況萍膛。

    void lock();
    
    boolean tryLock(); 
    
    void lockInterruptibly() throws InterruptedException; 
    
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException; 
    
    void unlock();
   

非公平的ReentrantLock

ReentrantLock的lock方法

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

ReentrantLock的lock方法內(nèi)調(diào)用了sync的lock方法。NonfairSync的實(shí)現(xiàn)如下所示嚷堡。

NonfairSync的lock方法

final void lock() {
    //注釋1處蝗罗,
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        //注釋2處
        acquire(1);
}

注釋1處,首先調(diào)用AQS的compareAndSetState方法以CAS的方式修改AQS的state變量蝌戒,如果修改成功串塑,說明當(dāng)前線程成功獲取了鎖,然后將當(dāng)前線程設(shè)置為鎖的持有者北苟。注意是以獨(dú)占模式持有鎖的桩匪。

protected final boolean compareAndSetState(int expect, int update) {
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}

如果修改AQS的state變量失敗,說明此時(shí)有其他線程已經(jīng)持有了鎖友鼻,那么就調(diào)用acquire(int arg)方法獲取鎖傻昙,注意我們傳入的參數(shù)是1。

AQS的acquire(int arg)方法

public final void acquire(int arg) {
  if (!tryAcquire(arg) &&
      //標(biāo)記為獨(dú)占模式
      acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
      selfInterrupt();
}

這個(gè)步驟可以分為3步(把大象放進(jìn)冰箱里需要幾步彩扔?)
步驟1: 調(diào)用tryAcquire(arg) 嘗試獲取鎖,獲取成功直接返回
步驟2: 嘗試獲取鎖失敗將當(dāng)前線程以獨(dú)占鎖的方式加入等待隊(duì)列
步驟3: 為已經(jīng)加入隊(duì)列中的線程嘗試獲取鎖

步驟1:調(diào)用tryAcquire(arg) 嘗試獲取鎖

AQS沒有實(shí)現(xiàn)這個(gè)方法妆档,需要子類來實(shí)現(xiàn)

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

我們看下ReentrantLock.NonfairSync類的實(shí)現(xiàn)

protected final boolean tryAcquire(int acquires) {
    //調(diào)用了父類ReentrantLock.Sync的nonfairTryAcquire(acquires)方法
    return nonfairTryAcquire(acquires);
}

ReentrantLock.Sync的nonfairTryAcquire(acquires)方法

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    //獲取同步狀態(tài)值
    int c = getState();
    //注釋1處
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        //注釋2處
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    //注釋3處
    return false;
}
 

在注釋1處,如果同步狀態(tài)值為0虫碉,說明沒有線程持有鎖贾惦,那么就以CAS的方式修改AQS的state變量,如果修改成功敦捧,說明當(dāng)前線程成功獲取了鎖须板,然后將當(dāng)前線程設(shè)置為鎖的持有者,然后返回true兢卵,獲取鎖成功逼纸。

注釋2處,如果有線程持有鎖济蝉,并且持有鎖的線程是當(dāng)前線程杰刽,那么就將同步狀態(tài)值加1然后重新賦值給同步狀態(tài)值state菠发,然后返回true,獲取鎖成功贺嫂。

AQS的setState(int newState)方法

protected final void setState(int newState) {
      state = newState;
}

注意:調(diào)用這個(gè)方法的前提是當(dāng)前線程就是鎖的持有者滓鸠,所以可以修改state值,并不需要方法同步第喳。

注釋3處糜俗,獲取鎖失敗。

到此步驟1結(jié)束曲饱,如果步驟1中獲取鎖失敗悠抹,就會(huì)進(jìn)入步驟2。

步驟2: 獲取失敗將當(dāng)前線程加入等待隊(duì)列

在這里我們要提一下AQS的一個(gè)內(nèi)部類Node扩淀。Node類是對(duì)每一個(gè)等待獲取鎖的線程的封裝楔敌,其包含了線程本身及其等待狀態(tài),如是否被阻塞驻谆、是否等待喚醒卵凑、是否已經(jīng)被取消等。還包括指向當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)的指針和后繼節(jié)點(diǎn)的指針(雙向鏈表)胜臊。Node類的成員變量waitStatus則表示當(dāng)前Node節(jié)點(diǎn)的等待狀態(tài)勺卢,共有5種取值CANCELLED、SIGNAL象对、CONDITION黑忱、PROPAGATE、0勒魔。

static final int CANCELLED =  1;

static final int SIGNAL    = -1;

static final int CONDITION = -2;

static final int PROPAGATE = -3;

//默認(rèn)是0
volatile int waitStatus;
  • CANCELLED:表示當(dāng)前節(jié)點(diǎn)由于超時(shí)或者中斷而被取消甫煞。進(jìn)入該狀態(tài)后的節(jié)點(diǎn)狀態(tài)將不會(huì)再變化。特別的沥邻,取消節(jié)點(diǎn)的線程不會(huì)被再次阻塞危虱。

  • SIGNAL:當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)被阻塞了羊娃,所以當(dāng)前節(jié)點(diǎn)在釋放鎖或者取消的時(shí)候必須喚醒后繼節(jié)點(diǎn)唐全。后繼節(jié)點(diǎn)入隊(duì)時(shí),會(huì)將父節(jié)點(diǎn)的狀態(tài)更新為SIGNAL蕊玷。

  • CONDITION:表示節(jié)點(diǎn)正在一個(gè)條件隊(duì)列中邮利,本篇文章暫時(shí)忽略。

  • PROPAGATE:共享模式下垃帅,節(jié)點(diǎn)不僅會(huì)喚醒其后繼節(jié)點(diǎn)延届,同時(shí)也可能會(huì)喚醒后繼節(jié)點(diǎn)的后繼節(jié)點(diǎn)。比如當(dāng)前節(jié)點(diǎn)釋放了10個(gè)資源贸诚,當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)只需要6個(gè)節(jié)點(diǎn)方庭,那么當(dāng)前節(jié)點(diǎn)在釋放的時(shí)候就會(huì)喚醒后繼節(jié)點(diǎn)和后繼節(jié)點(diǎn)的后繼節(jié)點(diǎn)厕吉。

  • 0:新節(jié)點(diǎn)進(jìn)入等待隊(duì)列時(shí)的默認(rèn)狀態(tài)。

注意械念,負(fù)值表示節(jié)點(diǎn)處于有效等待狀態(tài)头朱,而正值表示節(jié)點(diǎn)已被取消。所以源碼中很多地方用>0龄减、<0來判斷節(jié)點(diǎn)的狀態(tài)是否正常项钮。

private Node addWaiter(Node mode) {
    //以獨(dú)占模式加入等待隊(duì)列
    Node node = new Node(Thread.currentThread(), mode);
    // 先嘗試最快的入隊(duì)方式
    Node pred = tail;
    //注釋1處
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    //注釋2處
    enq(node);
    return node;
}

在注釋1處,如果尾節(jié)點(diǎn)不為null希停,就直接將當(dāng)前節(jié)點(diǎn)使用CAS的方式更新為尾節(jié)點(diǎn)烁巫,如果更新成功就返回node,這是最快的入隊(duì)方式宠能。

如果尾節(jié)點(diǎn)為null亚隙,或者將當(dāng)前節(jié)點(diǎn)使用CAS的方式更新為尾節(jié)點(diǎn)失敗,就調(diào)用注釋2處的enq(final Node node)方法將node加入隊(duì)列棍潘。

AQS的enq(final Node node)方法恃鞋,注意,這個(gè)方法是一個(gè)無限循環(huán)亦歉,只有成功將加入到隊(duì)列尾部才會(huì)返回恤浪。

private Node enq(final Node node) {
    for (;;) {//有一點(diǎn)疑問,這個(gè)for循環(huán)什么時(shí)候退出呢肴楷?
        Node t = tail;
        if (t == null) { // 如果隊(duì)列不存在水由,就新建一個(gè)node然后初始化隊(duì)列
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

首先如果尾節(jié)點(diǎn)為null,說明隊(duì)列此時(shí)還不存在赛蔫,就新建一個(gè)節(jié)點(diǎn)然后以CAS的方式將新創(chuàng)建的節(jié)點(diǎn)設(shè)置為頭節(jié)點(diǎn)砂客,如果成功則讓尾節(jié)點(diǎn)也指向node。如果如果尾節(jié)點(diǎn)不為null呵恢,就以CAS的方式將node更新為尾節(jié)點(diǎn)鞠值。

注意,這個(gè)方法是一個(gè)無限循環(huán)渗钉,只有成功將node加入到隊(duì)列尾部才會(huì)返回彤恶。

將node加入到等待隊(duì)列成功以后會(huì)進(jìn)入到AQS的acquire(int arg)方法的步驟3

步驟3: 為已經(jīng)加入隊(duì)列中的線程嘗試獲取鎖

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;//標(biāo)記是否成功獲取鎖
    try {
        boolean interrupted = false;//標(biāo)記線程是否被中斷
        //無限循環(huán)
        for (;;) {//注釋1處
            //獲取前驅(qū)節(jié)點(diǎn)
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                //如果前驅(qū)節(jié)點(diǎn)是head并且嘗試獲取鎖成功,就將當(dāng)前節(jié)點(diǎn)更新為head節(jié)點(diǎn)
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //注釋2處
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                //標(biāo)記線程在阻塞過程中是否被中斷
                interrupted = true;
        }
    } finally {//注釋3處
        if (failed)
            cancelAcquire(node);
    }
}

注釋1處鳄橘,如果前驅(qū)節(jié)點(diǎn)是head并且調(diào)用tryAcquire(int arg)方法獲取鎖成功声离,就將當(dāng)前節(jié)點(diǎn)更新為head節(jié)點(diǎn),然后返回瘫怜。

private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}

在setHead方法中將node的thread和prev變量都置為了null术徊,是為了幫助GC和避免不必要的喚醒和遍歷。

在注釋2處鲸湃,如果獲取鎖失敗后則判斷是否應(yīng)該阻塞當(dāng)前線程

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //獲取前驅(qū)節(jié)點(diǎn)的狀態(tài)
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        /*
         * node節(jié)點(diǎn)已經(jīng)設(shè)置了狀態(tài)告訴前驅(qū)節(jié)點(diǎn)在釋放鎖的時(shí)候通知自己赠涮,所以node節(jié)點(diǎn)可以被安全的阻塞子寓。
         */
        return true;
    if (ws > 0) {
        /*
         * 前驅(qū)節(jié)點(diǎn)已經(jīng)被取消了,向前尋找狀態(tài)有效的前驅(qū)節(jié)點(diǎn)笋除,然后將node設(shè)置為有效前驅(qū)節(jié)點(diǎn)的后繼節(jié)點(diǎn)别瞭。
         * 注意:已經(jīng)被取消的節(jié)點(diǎn)會(huì)被GC,這些節(jié)點(diǎn)相當(dāng)于一個(gè)無引用鏈株憾。
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
         * 以CAS的方式更新前驅(qū)節(jié)點(diǎn)的waitStatus為Node.SIGNAL蝙寨,告訴前驅(qū)節(jié)點(diǎn)在釋放鎖的時(shí)候通知自己。
         * 可能會(huì)失敗嗤瞎。
         */
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

如果shouldParkAfterFailedAcquire方法返回false墙歪,那么重新循環(huán)。如果返回true則調(diào)用parkAndCheckInterrupt方法贝奇。

AQS的parkAndCheckInterrupt()方法虹菲,注意這個(gè)方法會(huì)阻塞線程,并在線程 被喚醒后掉瞳,通過調(diào)用Thread.interrupted()返回在阻塞過程中線程是否被中斷毕源。

private final boolean parkAndCheckInterrupt() {
    //注釋1處
    LockSupport.park(this);
    //喚醒后,返回在阻塞過程中是否被中斷
    return Thread.interrupted();
}

LockSupport.park(this);

注釋1處這行代碼會(huì)阻塞當(dāng)前線程陕习,Thread.interrupted()這行代碼就不會(huì)執(zhí)行了霎褐,只有被喚醒后Thread.interrupted()這行代碼才會(huì)執(zhí)行。

在線程被喚醒后该镣,返回在阻塞過程中是否被中斷冻璃。注意Thread.interrupted()方法會(huì)將線程的中斷狀態(tài)清空。

當(dāng)線程被喚醒后损合,也會(huì)重新循環(huán)省艳。

到現(xiàn)在AQS的acquire方法就結(jié)束了。

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

這里要注意一下嫁审,因?yàn)?code>Thread.interrupted()方法會(huì)將線程的中斷狀態(tài)清空跋炕,所以我們這里要判斷一下,如果線程在阻塞過程中被中斷了律适,我們?cè)谶@里要調(diào)用selfInterrupt()方法來中斷當(dāng)前線程辐烂,也就是將當(dāng)前線程的中斷狀態(tài)置為true。

現(xiàn)在總結(jié)一下AQS的acquire(int arg)方法的流程擦耀。

  1. 調(diào)用子類的tryAcquire(int acquires)方法先嘗試獲取鎖棉圈,如果成功則直接返回涩堤;
  2. 獲取失敗眷蜓,則調(diào)用addWaiter(Node mode)方法將該線程加入等待隊(duì)列的尾部,并標(biāo)記為獨(dú)占模式胎围;
  3. 將該線程加入等待隊(duì)列后吁系,調(diào)用acquireQueued(final Node node, int arg)方法來嘗試獲取鎖德召,在這個(gè)過程中,線程可能會(huì)被多次阻塞汽纤、喚醒上岗。如果成功獲取鎖,就將當(dāng)前節(jié)點(diǎn)更新為head節(jié)點(diǎn)蕴坪,然后返回肴掷。如果在整個(gè)等待過程中被中斷過,則返回true背传,否則返回false呆瞻。
  4. 如果線程在等待過程中被中斷過,它是不響應(yīng)的径玖。只是獲取鎖后才再進(jìn)行自我中斷selfInterrupt()痴脾,將中斷補(bǔ)上。

到此梳星,非公平的ReentrantLock的lock() 方法分析完畢赞赖。

boolean tryLock(); 

void lockInterruptibly() throws InterruptedException; 

boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

這三個(gè)獲取鎖的方法過程也是類似的,就不進(jìn)行分析了冤灾,接下來看一看非公平的ReentrantLock釋放鎖的過程前域。

ReentrantLock的unlock()方法

public void unlock() {
    //調(diào)用AQS的release方法
    sync.release(1);
}

其實(shí)我們這里可以看到,一個(gè)線程可以多次獲取鎖(可重入鎖)韵吨,每獲取一次鎖就會(huì)將state加1话侄,每釋放一次鎖,就會(huì)將state減1学赛,當(dāng)前線程將state減到0的時(shí)候年堆,說明當(dāng)前線程釋放了鎖。

AQS的release(int arg)方法

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            //喚醒后繼節(jié)點(diǎn)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

首先調(diào)用tryRelease(int arg)方法,AQS沒有實(shí)現(xiàn)這個(gè)方法盏浇,我們直接看ReentrantLock.Sync的實(shí)現(xiàn)

protected final boolean tryRelease(int releases) {
    //同步狀態(tài)每次減1
    int c = getState() - releases;
   //如果當(dāng)前線程不是鎖的持有者变丧,拋出異常,沒資格釋放鎖绢掰,哈哈
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {//同步狀態(tài)為0痒蓬,表示成功釋放了鎖
        free = true;
        setExclusiveOwnerThread(null);
    }
    //更新同步狀態(tài)的值
    setState(c);
    return free;
}

方法首先將同步狀態(tài)值減去1,如果如果當(dāng)前線程不是鎖的持有者滴劲,拋出異常攻晒。如果同步狀態(tài)值減到了0,說明表示成功釋放了鎖班挖,然后我們將鎖的持有者設(shè)置為null鲁捏,最后更新同步狀態(tài)值,然后返回萧芙。

如果tryRelease返回了false给梅,說明沒有成功釋放鎖假丧,如果返回true,表示成功釋放了鎖动羽,那么我們要喚醒后繼節(jié)點(diǎn)包帚。

private void unparkSuccessor(Node node) {
    /*
     * 首先,它會(huì)檢查節(jié)點(diǎn)的waitStatus字段运吓。如果waitStatus小于0
     *(表示節(jié)點(diǎn)的后繼節(jié)點(diǎn)需要喚醒)渴邦,那么它會(huì)嘗試將waitStatus設(shè)置為0。
     */
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * 然后拘哨,它會(huì)找到需要喚醒的節(jié)點(diǎn)几莽。這個(gè)節(jié)點(diǎn)通常是當(dāng)前節(jié)點(diǎn)的直接后繼節(jié)點(diǎn),
     * 但是如果后繼節(jié)點(diǎn)被取消或者為null宅静,那么它會(huì)從隊(duì)列的尾部開始向前遍歷章蚣,
     * 找到第一個(gè)未被取消的節(jié)點(diǎn)。
     */
    Node s = node.next;
    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;
    }
    //最后姨夹,如果找到了需要喚醒的節(jié)點(diǎn)纤垂,那么它會(huì)調(diào)用LockSupport.unpark方法來喚醒節(jié)點(diǎn)對(duì)應(yīng)的線程。
    if (s != null)
        LockSupport.unpark(s.thread);
}

被喚醒的線程會(huì)從上面的parkAndCheckInterrupt方法中第二行代碼恢復(fù)執(zhí)行

private final boolean parkAndCheckInterrupt() {
    //注釋1處
    LockSupport.park(this);
    //喚醒后磷账,在這里恢復(fù)執(zhí)行峭沦,返回在阻塞過程中是否被中斷
    return Thread.interrupted();
}

總結(jié)一下AQS的release(int arg)方法的流程。

release方法每次釋放鎖就會(huì)將state值減1逃糟,如果徹底釋放了(即state==0)吼鱼,就會(huì)喚醒等待隊(duì)列里的其他線程來獲取鎖。

看完了非公平的ReentrantLock獲取鎖和釋放鎖的過程绰咽,接下來我們看看公平的ReentrantLock獲取鎖和釋放鎖過程菇肃。

公平的ReentrantLock

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

當(dāng)我們使用上面的構(gòu)造函數(shù)創(chuàng)建ReentrantLock實(shí)例的時(shí)候,如果傳入的參數(shù)是true取募,那么構(gòu)建的是公平的ReentrantLock

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

ReentrantLock.FairSync的lock方法

final void lock() {
    //獲取鎖
    acquire(1);
}

AQS的acquire(int arg)方法

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

這個(gè)步驟和非公平策略獲取鎖是一樣的可以分為3步
步驟1: 調(diào)用tryAcquire(arg) 嘗試獲取鎖琐谤,獲取成功直接返回
步驟2: 獲取失敗將當(dāng)前線程以獨(dú)占鎖的方式加入等待隊(duì)列
步驟3: 為已經(jīng)加入隊(duì)列中的線程嘗試獲取鎖

步驟1: 調(diào)用tryAcquire(arg) 嘗試獲取鎖

ReentrantLock.FairSync的tryAcquire(int acquires)方法

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        //注釋1處
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
       }
    }
    //注釋2處
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false; 
}

在上面方法的注釋1處,c==0表示沒有線程持有鎖玩敏,首先調(diào)用hasQueuedPredecessors方法判斷等待隊(duì)列里面是否有節(jié)點(diǎn)斗忌,如果有等待節(jié)點(diǎn),返回true旺聚,那么當(dāng)前線程就不去獲取鎖(體現(xiàn)了公平)织阳。如果沒有等待節(jié)點(diǎn)并且以CAS的方式獲取鎖成功則將當(dāng)前線程賦值為持有鎖的線程,返回true砰粹。

在注釋2處唧躲,c!=0表示有線程持有鎖,如果是當(dāng)前線程持有鎖的話,那么就將同步狀態(tài)值加1惊窖,返回true。

步驟2和步驟3和非公平的ReentrantLock是一樣的厘贼,就不再贅述了界酒。

公平的ReentrantLock和非公平的ReentrantLock的release(int arg)方法也是一樣的就不再贅述了。

公平的ReentrantLock和非公平的ReentrantLock的差異

  1. 公平的ReentrantLock和非公平的ReentrantLock的差異由ReentrantLock.FairSyncReentrantLock.NonfairSync體現(xiàn)嘴秸。非公平鎖總會(huì)先嘗試獲取鎖毁欣。如下圖所示:左邊是公平鎖,右邊是非公平鎖岳掐。
    lock方法
image.png

tryAcquire 方法

image.png
  1. 如果等待隊(duì)列里有節(jié)點(diǎn)等待獲取鎖凭疮,公平的ReentrantLock就會(huì)直接進(jìn)入等待隊(duì)列排隊(duì)。非公平的ReentrantLock無論等待隊(duì)列里是否有節(jié)點(diǎn)等待獲取鎖串述,總是先嘗試獲取鎖执解,如果獲取失敗才進(jìn)入等待隊(duì)列進(jìn)行排隊(duì)。

結(jié)尾:本篇文章通過ReentrantLock引出了AQS是如何幫助ReentrantLock實(shí)現(xiàn)的獲取鎖和釋放鎖的纲酗。下一篇文章打算分析一下AQS是如何幫助ReentrantLock實(shí)現(xiàn)Condition功能的衰腌。

參考鏈接:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市觅赊,隨后出現(xiàn)的幾起案子右蕊,更是在濱河造成了極大的恐慌,老刑警劉巖吮螺,帶你破解...
    沈念sama閱讀 206,602評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饶囚,死亡現(xiàn)場離奇詭異,居然都是意外死亡鸠补,警方通過查閱死者的電腦和手機(jī)萝风,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來紫岩,“玉大人闹丐,你說我怎么就攤上這事”灰颍” “怎么了卿拴?”我有些...
    開封第一講書人閱讀 152,878評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長梨与。 經(jīng)常有香客問我堕花,道長,這世上最難降的妖魔是什么粥鞋? 我笑而不...
    開封第一講書人閱讀 55,306評(píng)論 1 279
  • 正文 為了忘掉前任缘挽,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘壕曼。我一直安慰自己苏研,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,330評(píng)論 5 373
  • 文/花漫 我一把揭開白布腮郊。 她就那樣靜靜地躺著摹蘑,像睡著了一般。 火紅的嫁衣襯著肌膚如雪轧飞。 梳的紋絲不亂的頭發(fā)上衅鹿,一...
    開封第一講書人閱讀 49,071評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音过咬,去河邊找鬼大渤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛掸绞,可吹牛的內(nèi)容都是我干的泵三。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼衔掸,長吁一口氣:“原來是場噩夢啊……” “哼切黔!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起具篇,我...
    開封第一講書人閱讀 37,006評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤纬霞,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后驱显,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诗芜,經(jīng)...
    沈念sama閱讀 43,512評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,965評(píng)論 2 325
  • 正文 我和宋清朗相戀三年埃疫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了伏恐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,094評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡栓霜,死狀恐怖翠桦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情胳蛮,我是刑警寧澤销凑,帶...
    沈念sama閱讀 33,732評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站仅炊,受9級(jí)特大地震影響斗幼,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜抚垄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,283評(píng)論 3 307
  • 文/蒙蒙 一蜕窿、第九天 我趴在偏房一處隱蔽的房頂上張望谋逻。 院中可真熱鬧,春花似錦桐经、人聲如沸毁兆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽悯嗓。三九已至窄坦,卻和暖如春樟插,著一層夾襖步出監(jiān)牢的瞬間怔鳖,已是汗流浹背摹菠。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評(píng)論 1 262
  • 我被黑心中介騙來泰國打工盒卸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人次氨。 一個(gè)月前我還...
    沈念sama閱讀 45,536評(píng)論 2 354
  • 正文 我出身青樓蔽介,卻偏偏與公主長得像,于是被迫代替她去往敵國和親煮寡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子虹蓄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,828評(píng)論 2 345

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

  • ReentrantLock 介紹 一個(gè)可重入的互斥鎖,它具有與使用{synchronized}方法和語句訪問的隱式...
    tomas家的小撥浪鼓閱讀 4,040評(píng)論 1 4
  • 重入鎖Reentrantlock Lock接口 先大概看一看lock接口 而我們一般使用Reentrantlock...
    faunjoe閱讀 2,144評(píng)論 0 11
  • AQS簡介 AQS:AbstractQueuedSynchronizer幸撕,即隊(duì)列同步器薇组。它是構(gòu)建鎖或者其他同步組件...
    lijiaccy閱讀 620評(píng)論 0 0
  • 『耕』 出品/簡垃圾 螻蟻曉天時(shí), 春農(nóng)總未遲坐儿。 一灣堤上水律胀, 破土潤田知。 2019年04月21日
    北平永勝閱讀 185評(píng)論 0 0
  • 生活中有這樣的一個(gè)現(xiàn)象:作為傾聽者貌矿,對(duì)于演講者表達(dá)的一個(gè)意思炭菌,不同的人,會(huì)有不同的理解逛漫,更有甚者會(huì)得出截然相反的觀...
    丁昆朋閱讀 609評(píng)論 6 2