源碼分析:同步基礎(chǔ)框架——AbstractQueuedSynchronizer(AQS)

簡介

AQS 全稱是 AbstractQueuedSynchronizer挽牢,位于java.util.concurrent.locks 包下面,AQS 提供了一個基于FIFO的隊列和維護了一個狀態(tài)state變量賴表示狀態(tài),可以作為構(gòu)建鎖或者其他相關(guān)同步裝置的基礎(chǔ)框架。AQS 支持兩種模式:共享模式 和 排他模式,當(dāng)它被定義為一個排他模式時,其他線程對其的獲取就被阻止庸娱,而共享模式對于多個線程獲取都可以成功。之所以說它是一個同步基礎(chǔ)框架是因為很多同步類里面都用到了AQS谐算,比如 ReentrantLock 中的內(nèi)部類同步器Sync繼承至AQS熟尉,ReentrantReadWriteLock中的同步器也是繼承至AQS,還有 Semaphore 洲脂、CountDownLatch等都是基于AQS來實現(xiàn)的斤儿。

核心源碼

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

AQS 繼承了 AbstractOwnableSynchronizer, AbstractOwnableSynchronizer 這個類比較簡單恐锦,就一個屬性 private transient Thread exclusiveOwnerThread 往果,用來標(biāo)識當(dāng)前獨占鎖的持有者線程,通俗的說就是哪個線程拿到了獨占鎖一铅,就調(diào)用AbstractOwnableSynchronizer 的方法把這個線程保存起來陕贮。源碼如下:

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
    ...
}

public abstract class AbstractOwnableSynchronizer implements java.io.Serializable {
  private transient Thread exclusiveOwnerThread;
  // 構(gòu)造方法,get set 方法省略潘飘。飘蚯。馍迄。
}

后面的分析中,會有大量的同步器在獲得鎖之后會調(diào)用setExclusiveOwnerThread(Thread) 方法來保存鎖的持有者線程局骤;

重要內(nèi)部類Node

static final class Node {
    volatile int waitStatus;
    volatile Node prev;
    volatile Node next;
    volatile Thread thread;
    Node nextWaiter;
}

以上五個成員變量主要負責(zé)保存該節(jié)點的線程引用攀圈,同步隊列的前驅(qū)和后繼節(jié)點,同時也包括了同步狀態(tài)峦甩。

屬性解釋:

waitStatus:表示節(jié)點的狀態(tài)赘来。其中包含的狀態(tài)有:

  1. CANCELLED,值為1凯傲,表示當(dāng)前的線程被取消犬辰;
  2. SIGNAL,值為-1冰单,表示當(dāng)前節(jié)點的后繼節(jié)點包含的線程需要運行幌缝,也就是unpark;
  3. CONDITION诫欠,值為-2涵卵,表示當(dāng)前節(jié)點在等待condition,也就是在condition隊列中荒叼;
  4. PROPAGATE瞳别,值為-3瘦棋,表示當(dāng)前場景下后續(xù)的acquireShared能夠得以執(zhí)行潮针;
  5. 值為0谅海,表示當(dāng)前節(jié)點在sync隊列中,等待著獲取鎖嫁乘。

prev:前驅(qū)節(jié)點昆婿,比如當(dāng)前節(jié)點被取消,那就需要前驅(qū)節(jié)點和后繼節(jié)點來完成連接

next:后繼節(jié)點

thread:入隊列時的當(dāng)前線程

nextWaiter:存儲condition隊列中的后繼節(jié)點

重要屬性:同步隊列和同步狀態(tài)

節(jié)點成為同步隊列和 condition 條件隊列構(gòu)建的基礎(chǔ)蜓斧,同步器擁有三個成員變量:頭結(jié)點head挖诸、尾節(jié)點tail和同步狀態(tài)state

private transient volatile Node head;
private transient volatile Node tail;
private volatile int state;

對于新的獲取鎖請求法精,形成Node節(jié)點,掛載到隊列的尾部痴突;對于鎖資源的釋放都是從隊列的頭部進行操作的搂蜓。

      +------+  prev +-----+       +-----+
 head |      | <---- |     | <---- |     |  tail
      +------+       +-----+       +-----+

可以重寫的API

實現(xiàn)自定義同步器時,需要使用同步器提供的getState()辽装、setState()和compareAndSetState()方法來控制同步狀態(tài)帮碰。

方法1:protected boolean tryAcquire(int arg)

描述:已排它模式獲取同步狀態(tài)。這個方法的實現(xiàn)需要查詢前狀態(tài)是否允許獲取拾积,然后再進compareAndSetState()修改狀態(tài)殉挽,修改成功代表成功獲得鎖丰涉。

方法2:protected boolean tryRelease(int arg)

描述:釋放鎖,也就是釋放同步狀態(tài)state的值到初始狀態(tài)斯碌,一般是0一死。

方法3:protected int tryAcquireShared(int arg)

描述:共享模式下獲取同步狀態(tài),一般可以用來做共享鎖傻唾,或者用作限制資源最多同時被訪問多少次投慈。

方法4:protected boolean tryReleaseShared(int arg)

描述:共享模式下釋放同步狀態(tài)。

方法5:protected boolean isHeldExclusively()

描述:在排它模式下冠骄,返回同步狀態(tài)是否被占用伪煤,比如我們可以實現(xiàn)返回邏輯為 getState() == 1,為true的話說明資源已經(jīng)被占用了凛辣。

其他代碼我們通過自己實現(xiàn)簡單的排他鎖案例來進行具體的詳細分析

基于AQS實現(xiàn)的排他鎖

一抱既、定義一個MyAQSLock類

public class MyAQSLock{
}

二、定義一個內(nèi)部類Sync做為同步器扁誓,繼承自AbstractQueuedSynchronizer

public class MyAQSLock{
    class Sync extends AbstractQueuedSynchronizer{
    }
}

三防泵、重寫同步器部分API

因為我們要實現(xiàn)的是排它鎖的功能,意思就是同一時刻只能有一個線程獲得鎖跋理,所以只需要重寫tryAcquire择克、tryReleaseisHeldExclusively方法即可。

class Sync extends AbstractQueuedSynchronizer{
        @Override
        protected boolean **tryAcquire**(int acquires){
            // 入?yún)⒅荒転?
            **assert acquires == 1;
            // 使用CAS的方式修改state值前普,修改成功代表成功獲得鎖
            if(compareAndSetState(0,1)){
                // 修改鎖的持有者為當(dāng)前線程
                setExclusiveOwnerThread(Thread.currentThread());
                // 返回true肚邢,表示成功獲得鎖
                return true;
            }
            // 返回false,沒有獲得鎖
            return false;
        }

        @Override
        protected boolean **tryRelease**(int releases){
            assert releases == 1;
            if (getState() == 0){
                // 已經(jīng)被釋放了
                throw new IllegalMonitorStateException();
            }
            // lock() 和 unlock() 一般都是成對出現(xiàn)的拭卿,所以這里不需要同步語句骡湖,可以直接修改state值為0
            setState(0);
            return true;
        }
        @Override
        protected boolean **isHeldExclusively**() {
            // 返回true,說明已經(jīng)有其他線程獲得鎖
            return getState() == 1;
        }

    }

三峻厚、定義鎖和解鎖方法

public class MyAQSLock{

    private final Sync sync;
    MyAQSLock(){
        sync = new Sync();
    }
        class Sync extends AbstractQueuedSynchronizer{
        ...
        }
    public void lock(){
        // 調(diào)用同步器响蕴,獲得鎖
        sync.acquire(1);
    }

    public boolean tryLock(){
              // 嘗試獲得鎖,如果沒有獲取到鎖惠桃,則立即返回false
        return sync.tryAcquire(1);
    }
 
    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException{
        // 嘗試獲得鎖浦夷,如果沒有獲取到鎖,允許等待一段時間
        return sync.tryAcquireNanos(1,unit.toNanos(timeout));
    }
 
    public void unLock(){
        // 解鎖
        sync.release(1);
    } 

    public boolean isLocked(){
        // 判斷鎖是否已經(jīng)被占用
        return sync.isHeldExclusively();
    }
}

四辜王、測試我們的鎖

static int count = 0;
public static void main(String[] args) throws InterruptedException{
    MyAQSLock myAQSLock = new MyAQSLock();
    CountDownLatch countDownLatch = new CountDownLatch(1000);
    IntStream.range(0,1000).forEach(i->new Thread(()->{
        myAQSLock.lock();
        try{
            IntStream.range(0,10000).forEach(j->{
                count++;
            });
        }finally{
            myAQSLock.unLock();
        }
        countDownLatch.countDown();
    }).start());
    countDownLatch.await();
    System.out.println(count);
}

最后正確輸出10000000劈狐,說明我們實現(xiàn)的鎖是有效的。但是要注意我們自己寫的這個鎖是不支持重入的呐馆。

代碼實現(xiàn)分析

獲得鎖:public void lock()

lock()方法會調(diào)用sync.acquire(int)方法肥缔,acquire在AQS里面,方法被final修飾汹来,作為基礎(chǔ)框架邏輯部分续膳,不允許被繼承改艇,源碼展示:

public final void acquire(int arg) {
    // tryAcquire 是我們自己實現(xiàn)的方法,具體實現(xiàn)看上面
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        // acquireQueued 返回true表示線程被中斷了坟岔,中斷當(dāng)前線程
        selfInterrupt();
}

上面acquire()主要的邏輯有:

  1. 嘗試獲得鎖谒兄,調(diào)用tryAcquire(arg)方法,該方法的邏輯在我們自定義的MyAQSLock類中炮车,我們利用了compareAndSetState來保證state字段的原子性舵变。

  2. 如果tryAcquire返回true的話,if分支會直接退出瘦穆,表示成功獲得鎖纪隙,繼續(xù)執(zhí)行調(diào)用lock() 方法后面的邏輯;

  3. 如果tryAcquire返回false的話扛或,表示沒有獲得鎖绵咱,會繼續(xù)執(zhí)行 && 后面的邏輯;

  4. 首先會調(diào)用addWaiter(Node.EXCLUSIVE)方法為當(dāng)前線程創(chuàng)建排隊節(jié)點熙兔,并加入到隊列悲伶,Node.EXCLUSIVE代表這個節(jié)點是獨占排他鎖的意思,具體源碼如下:

    private Node addWaiter(Node mode) {
        // 為當(dāng)前線程創(chuàng)建一個節(jié)點住涉,最后會返回出去這個節(jié)點
        Node node = new Node(Thread.currentThread(), mode);
        // 隊列不為空時麸锉,快速嘗試在同步隊列尾部添加當(dāng)前節(jié)點,如果失敗了會進入enq方法自旋入隊
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 上面入隊失敗了舆声,或者是pred為空(第一個排隊的線程進來)花沉,繼續(xù)自旋入隊
        enq(node);
        return node;
    }
    
    private Node enq(final Node node) {
        // 空的for循環(huán),自旋操作媳握,直到成功把節(jié)點加入到同步隊列
        for (;;) {
            // 同步隊列尾巴
            Node t = tail;
            if (t == null) { // Must initialize
                 // 尾巴是空的碱屁,還沒有初始化, 第一個排隊的線程進來的話蛾找,隊頭隊尾都是同一個節(jié)點
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                // 進入到這里娩脾,說明同步隊列已經(jīng)有線程在排隊了
                // 當(dāng)前節(jié)點前驅(qū)直接指向同步隊里的尾節(jié)點
                node.prev = t;
                // CAS 修改尾節(jié)點為當(dāng)前節(jié)點
                if (compareAndSetTail(t, node)) {
                    // t還是老的尾節(jié)點,修改新的尾節(jié)點后老的尾節(jié)點的下一個節(jié)點就是當(dāng)前節(jié)點打毛,建立他們的聯(lián)系
                    t.next = node;
                    // 成功把當(dāng)前節(jié)點加入到了同步隊列柿赊,返回當(dāng)前節(jié)點,退出自旋
                    return t;
                }
            }
        }
    }
    

    addWaiter()方法總結(jié):首先會快速嘗試一次在隊列的尾部添加當(dāng)前線程節(jié)點幻枉,如果失敗的話(在這個時候碰声,可能有新的線程也沒有獲得鎖,并且跑在當(dāng)前的前面加入到同步隊列了)展辞,會調(diào)用enq邏輯進行自旋加入隊尾,直到成功加入隊列為止万牺。

  5. 再次嘗試從同步隊列獲得鎖acquireQueued(node,arg)

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            // 自旋操作
            for (;;) {
                // 當(dāng)前節(jié)點的上一個節(jié)點
                final Node p = node.predecessor(); //**①**
                // 如果前驅(qū)節(jié)點是頭結(jié)點罗珍,然后去嘗試獲得鎖洽腺,tryAcquire是我們自己實現(xiàn)的獲得鎖邏輯
                **if (p == head && tryAcquire(arg)) { //②**
                    // 當(dāng)前線程成功獲得鎖,當(dāng)前節(jié)點設(shè)置為頭結(jié)點
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    // 返回false覆旱,表示沒有被中斷
                    return interrupted;
                }
                // 到這里說明p != head 或者 **tryAcquire** 返回了false蘸朋,還是沒獲得鎖,這時候就需要阻塞線程了
                // shouldParkAfterFailedAcquire 如果線程應(yīng)阻塞扣唱,則返回true
                // parkAndCheckInterrupt  阻塞當(dāng)前線程 
                if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            // 節(jié)點被取消了
            if (failed)
                cancelAcquire(node);
        }
    }
    
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
         // SIGNAL值為-1藕坯,表示pred節(jié)點的后繼節(jié)點包含的線程需要運行,也就是unpark
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
            // 大于0的值只有1噪沙, 1表示線程被取消
            // 進入到這里說明 pred 節(jié)點被取消了炼彪,需要從同步隊列上刪掉它
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            // 一般初始時為0,設(shè)置成-1
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        // 返回false正歼,外面會一直自旋操作
        return false;
    }
    
    private final boolean parkAndCheckInterrupt() {
        // 調(diào)用底層的Unsafe一直阻塞線程
        LockSupport.park(this);
        // 被unpark喚醒之后辐马,會繼續(xù)回去自旋獲得鎖,并返回線程在此期間是否有被中斷
        return Thread.interrupted();
    }
    

    lock方法總結(jié):

    1. 嘗試獲得鎖局义,方法tryAcquire
      1. 成功獲得鎖喜爷,直接退出;沒有獲得鎖萄唇,繼續(xù)執(zhí)行檩帐;
    2. 新建排隊節(jié)點,并加入到同步隊列另萤,方法addWaiter
      1. 隊列不為空時湃密,嘗試一次快速直接把節(jié)點加入到隊列尾巴上;如果隊尾為空或者快速添加失敗仲墨,繼續(xù)執(zhí)行下面邏輯
      2. 自旋勾缭,直到成功把新建的節(jié)點加入到同步隊列;方法enq
        為什么要自旋呢目养?是因為在調(diào)這個方法的時候俩由,可能有其他想要獲得鎖線程沒有獲得鎖,并且已經(jīng)修改了尾節(jié)點癌蚁;
    3. 再次嘗試從同步隊列獲得鎖幻梯,方法acquireQueued
      1. 上面已經(jīng)把當(dāng)前線程的節(jié)點加入到隊列中了,理論上排隊的線程很多的話努释,它是馬上獲取不到鎖的
      2. 所以它會自旋判斷是否到了自己可以獲取鎖和CAS嘗試獲取鎖碘梢,關(guān)鍵代碼if (p == head && tryAcquire(arg))
        理論上當(dāng)前線程進入到了隊列排隊,只要隊列中還有更早的線程在它前面排隊伐蒂,當(dāng)前線程都不會比更早的線程先獲得鎖煞躬,所以在這一塊對于公平鎖和非公平鎖肯定都是公平的。
      3. 沒有資格去獲取鎖或沒有成功獲得鎖,就阻塞自己恩沛,方法parkAndCheckInterrupt
      4. 阻塞線程被喚醒在扰,自旋成功獲得鎖,排隊期間被中斷的線程也會獲得鎖雷客,之后退出自旋循環(huán)芒珠,返回線程的中斷狀態(tài);
    4. 線程如果被中斷了搅裙,中斷當(dāng)前線程皱卓,被中斷的線程還是會繼續(xù)執(zhí)行后面邏輯
    • 以上過程涉及到的技術(shù)點有:CAS,自旋部逮,隊列入隊娜汁,隊列刪除節(jié)點(被取消的節(jié)點),阻塞線程(LockSupport.park(this))

釋放鎖:public final boolean release(int arg)

unlock方法調(diào)用的是sync.release(1)甥啄,而release是AQS 方法中的方法存炮,表示將同步狀態(tài)設(shè)置回初始狀態(tài),將鎖釋放蜈漓。

public final boolean release(int arg) {
    // tryRelease 是我們自己的實現(xiàn)穆桂,就是把state字段設(shè)置成0,如果是可重入的融虽,只能慢慢減到初始狀態(tài)
      if (tryRelease(arg)) {
        // 進入到這里說明CAS 設(shè)置成功享完,也就代表鎖成功釋放了,需要喚醒隊列中的第一個排隊的節(jié)點線程
          Node h = head;
        // head 表示的是當(dāng)前獲得鎖的節(jié)點
          if (h != null && h.waitStatus != 0)
            // 喚醒頭結(jié)點的下一個節(jié)點
              unparkSuccessor(h);
          return true;
      }
      return false;
}
private void unparkSuccessor(Node node) {
    // 這里的node 是當(dāng)前持有鎖的節(jié)點
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);     
    // 找到頭結(jié)點的后繼節(jié)點
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
         // 大于0的狀態(tài)只有1有额,表示被取消了般又,如果被取消了,就繼續(xù)取下一個節(jié)點喚醒
        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);**
}

釋放鎖的邏輯比加鎖邏輯要簡單很多巍佑,主要邏輯有:

  1. 修改同步狀態(tài)為初始值茴迁,方法tryRelease(arg)

    這里我們自己實現(xiàn)的會直接將同步狀態(tài)設(shè)置為0,如果是支持可重入萤衰,就需要慢慢減了堕义;

  2. 釋放鎖儲層,喚醒頭結(jié)點(當(dāng)前獲得鎖的節(jié)點)的后繼節(jié)點:unparkSuccessor(h);
    找到頭結(jié)點的后繼節(jié)點中第一個沒有被取消的節(jié)點脆栋,并喚醒該節(jié)點所處線程

基于AQS實現(xiàn)自己的共享鎖

設(shè)計一個同步器倦卖,在同一時刻,只允許最多兩個線程能夠并行訪問椿争,超過限制的其他線程將進入阻塞狀態(tài)怕膛。

這個功能和 Semaphore 的功能很相似,這個學(xué)會了秦踪,以后看 Semaphore 的源碼也就很簡單了褐捻。

實現(xiàn)思路:

可以利用AQS 的API tryAcquireShared 實現(xiàn)獲得共享鎖掸茅,定義一個狀態(tài),允許的范圍為【0柠逞,1倦蚪,2】,狀態(tài)為2代表新的線程進入的時候需要阻塞等待

public class MyAqsSharedLock{

    // 定義最大共享值
    private final int maxSharedValue = 2;
    // 同步器
    private final Sync sync;

    MyAqsSharedLock(){
        // 構(gòu)造方法初始化同步器
        sync = new Sync();
    }
    // 基于AQS實現(xiàn)的同步器
    class Sync extends AbstractQueuedSynchronizer{
        @Override
        protected int tryAcquireShared(int arg){
            // 為什么要自旋呢边苹?因為可能滿足state的條件,但是CAS修改失敗
            while(true){
                int state = getState();
                // 檢查同步狀態(tài)是否達到最大值
                if(state >= maxSharedValue){
                    // 返回-1 表示沒有獲得鎖
                    return -1;
                }
                // CAS 修改同步狀態(tài)
                if(compareAndSetState(state,state + arg)){
                    // 修改成功裁僧,表示獲得了鎖个束,大于等于0表示獲得了鎖
                    return getState();
                }
            }
        }

        @Override
        protected boolean tryReleaseShared(int arg){
            // 為什么要自旋呢?因為可能滿足state的條件聊疲,但是CAS修改失敗
            while(true){
                int state = getState();
                // CAS 修改同步狀態(tài)茬底,修改成功返回true,失敗繼續(xù)自旋 
                if(compareAndSetState(state,state - arg)){
                    return true;
                }
            }
        }
    }
    /** 加鎖 */
    public void lock(){
        sync.acquireShared(1);
    }
    /** 解鎖 */
    public void unLock(){
        sync.releaseShared(1);
    }
}

測試方法:

5個線程循環(huán)打印輸出線程名和當(dāng)前時間

public static void main(String[] args){
        MyAqsSharedLock lock = new MyAqsSharedLock();
        IntStream.range(0,5).forEach(i -> new Thread(new Runnable(){
            @SneakyThrows
            @Override
            public void run(){
                while(true){
                    lock.lock();
                    try{
                        System.out.println(Thread.currentThread().getName()+":執(zhí)行获洲。阱表。。時間:"+ LocalDateTime.now());
                        TimeUnit.SECONDS.sleep(2);
                    }finally{
                        lock.unLock();
                        TimeUnit.SECONDS.sleep(1);
                    }

                }
            }
        },"T"+i).start());
    }

輸出結(jié)果示例:

T0:執(zhí)行贡珊。最爬。。時間:2020-10-30T17:45:45.117
T1:執(zhí)行门岔。爱致。。時間:2020-10-30T17:45:45.117
T3:執(zhí)行寒随。糠悯。。時間:2020-10-30T17:45:47.118
T2:執(zhí)行妻往。互艾。。時間:2020-10-30T17:45:47.118
T1:執(zhí)行讯泣。纫普。。時間:2020-10-30T17:45:49.119
T4:執(zhí)行判帮。局嘁。。時間:2020-10-30T17:45:49.119

會發(fā)現(xiàn)幾乎在同一時間最多只有2個線程在打印輸出晦墙,滿足我們的要求悦昵。

代碼實現(xiàn)分析

獲得共享鎖:public void lock()

共享鎖的 lock() 調(diào)用的是sync.acquireShared(1)acquireShared也在AQS里面晌畅,同樣被final修飾作為基礎(chǔ)框架邏輯部分但指,不允許被繼承,源碼展示:

public final void acquireShared(int arg) {
    // tryAcquireShared 是我們自己實現(xiàn)的邏輯,返回-1棋凳,表示沒有獲得鎖
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
// 沒有獲得共享拦坠,再次嘗試獲得鎖,和排他模式的acquireQueued方法非常相似
private void doAcquireShared(int arg) {
    // 新建節(jié)點剩岳,加入到隊列贞滨,和排他鎖模式一樣的入隊邏輯
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 當(dāng)前節(jié)點的前驅(qū)節(jié)點
            final Node p = node.predecessor();
            if (p == head) {
                // 前驅(qū)節(jié)點是頭結(jié)點,說明輪到咱獲得鎖了
                // 繼續(xù)調(diào)用我們自己的邏輯拍棕,CAS 獲得鎖
                int r = tryAcquireShared(arg);
                // 這里再次印證了晓铆,我們的tryAcquireShared返回值定義,負值是沒有獲得鎖绰播,>=0 表示成功獲得鎖
                if (r >= 0) {
                    // 設(shè)置新的頭結(jié)點骄噪,如果后面的排隊節(jié)點是共享模式的節(jié)點,直接喚醒它
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        // 中斷當(dāng)前線程
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            // 到這了蠢箩,說明要么沒有排隊到當(dāng)前線程链蕊,要么CAS獲取鎖失敗,那就只有阻塞線程了
            // shouldParkAfterFailedAcquire 如果線程應(yīng)阻塞谬泌,則返回true
            // parkAndCheckInterrupt  阻塞當(dāng)前線程admol滔韵, 具體實現(xiàn)分析可以看上面lock的分析
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        // 線程被取消,摘掉節(jié)點
        if (failed)
            cancelAcquire(node);
    }
}

獲取共享鎖總結(jié):

  1. 嘗試獲得鎖掌实,方法:tryAcquireShared
    1. 獲得鎖成功奏属,直接返回;獲得鎖失敗潮峦,繼續(xù)執(zhí)行下面邏輯囱皿;
  2. 再次嘗試獲得鎖,方法:doAcquireShared
    1. 新建排隊節(jié)點忱嘹,并加入到同步隊列嘱腥,方法:addWaiter,邏輯和獲得排它鎖的一致
    2. 自旋(嘗試獲得鎖拘悦,阻塞線程齿兔,等待被喚醒),直到成功獲得鎖

釋放共享鎖:public void unLock()

unlock方法調(diào)用的是sync.releaseShared(1)础米,releaseShared也是AQS 方法中的方法分苇,不允許被繼承,表示將同步狀態(tài)設(shè)置回初始狀態(tài)屁桑,將鎖釋放医寿。

public final boolean releaseShared(int arg) {
    // tryReleaseShared 我們自己實現(xiàn)的邏輯
    if (tryReleaseShared(arg)) {
        // 釋放鎖失敗,繼續(xù)釋放蘑斧,自旋直到釋放成功
        doReleaseShared();
        return true;
    }
    return false;
}

private void doReleaseShared() {
    // 自旋
    for (;;) {
        Node h = head;
        // 同步等待的隊列不為空
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            // 檢查狀態(tài)是否要喚醒下一個節(jié)點的線程
            if (ws == Node.SIGNAL) {
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                // 加入h節(jié)點是持有鎖的節(jié)點靖秩,會喚醒它的下一個節(jié)點線程
                unparkSuccessor(h);
            } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        // 理論上喚醒一個就會退出
        if (h == head)                   // loop if head changed
            break;
    }
}

總結(jié)

  1. AQS是Java中可以實現(xiàn)同步器功能的一個基礎(chǔ)框架须眷,我們自己也可以基于AQS實現(xiàn)想要的同步功能
  2. AQS 中用Node節(jié)點維護了一個雙向鏈表,用來保存排隊獲取鎖的線程沟突,已經(jīng)用來喚醒線程
  3. AQS 中為了一個state的同步狀態(tài)變量花颗,可以基于這個變量實現(xiàn)很多功能
  4. 實現(xiàn)AQS的幾個重要API,就可以實現(xiàn)一個簡單同步器的功能惠拭,其他像自旋扩劝,排隊,阻塞职辅,喚醒今野,AQS都已經(jīng)幫我們做好了

AQS 條件鎖分析請看這里:源碼分析:②ReentrantLock之條件鎖Condition

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市罐农,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌催什,老刑警劉巖涵亏,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蒲凶,居然都是意外死亡气筋,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門旋圆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來宠默,“玉大人,你說我怎么就攤上這事灵巧〔蠼茫” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵刻肄,是天一觀的道長瓤球。 經(jīng)常有香客問我,道長敏弃,這世上最難降的妖魔是什么卦羡? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮麦到,結(jié)果婚禮上绿饵,老公的妹妹穿的比我還像新娘。我一直安慰自己瓶颠,他們只是感情好拟赊,可當(dāng)我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著粹淋,像睡著了一般要门。 火紅的嫁衣襯著肌膚如雪虏肾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天欢搜,我揣著相機與錄音封豪,去河邊找鬼。 笑死炒瘟,一個胖子當(dāng)著我的面吹牛吹埠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播疮装,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼缘琅,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了廓推?” 一聲冷哼從身側(cè)響起刷袍,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎樊展,沒想到半個月后呻纹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡专缠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年雷酪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涝婉。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡哥力,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出墩弯,到底是詐尸還是另有隱情吩跋,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布渔工,位于F島的核電站钞澳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏涨缚。R本人自食惡果不足惜轧粟,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望脓魏。 院中可真熱鬧兰吟,春花似錦、人聲如沸茂翔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽珊燎。三九已至惭嚣,卻和暖如春遵湖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背晚吞。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工延旧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人槽地。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓迁沫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親捌蚊。 傳聞我的和親對象是個殘疾皇子集畅,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,077評論 2 355

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