Java并發(fā)編程——AQS源碼解析

Java并發(fā)編程——AQS源碼解析

  • 什么是AQS
  • AQS有什么用
  • AQS實(shí)現(xiàn)方式
一、AQS是什么仿畸?

AQS是一個(gè)基于先進(jìn)先出(FIFO)等待隊(duì)列的實(shí)現(xiàn)阻塞鎖和同步器的框架。AQS通過一個(gè)volatile int state變量來保存鎖的狀態(tài)谢肾。子類必須通過:

  • getState():獲取當(dāng)前的同步狀態(tài)
  • setState(int newState):設(shè)置當(dāng)前同步狀態(tài)
  • compareAndSetState(int expect,int update):使用CAS設(shè)置當(dāng)前狀態(tài)浦马,該方法能夠保證狀態(tài)設(shè)置的原子性。

三個(gè)方法修改獲取鎖的狀態(tài)值state殊校。

CAS (compare and swap) 比較并交換晴玖,就是將內(nèi)存值與預(yù)期值進(jìn)行比較,如果相等才將新值替換到內(nèi)存中为流,并返回true表示操作成功呕屎;如果不相等,則直接返回false表示操作失敗敬察。CAS操作大多都是靠CPU原語來實(shí)現(xiàn)秀睛。CAS操作經(jīng)常被用來實(shí)現(xiàn)無鎖數(shù)據(jù)結(jié)構(gòu),在java.util.concurrent包中就有很多這樣的數(shù)據(jù)結(jié)構(gòu):ConcurrentLinkedQueue莲祸、ConcurrentLinedDeque蹂安、ConcurrentHashMap、ConcurrentSkipListMap锐帜、ConcurrentSkipListSet田盈。

1、AQS支持的鎖的類別
AQS支持獨(dú)占鎖和共享鎖兩種缴阎。

  • 獨(dú)占鎖:鎖在一個(gè)時(shí)間點(diǎn)只能被一個(gè)線程占有允瞧。根據(jù)鎖的獲取機(jī)制,又分為“公平鎖”和“非公平鎖”蛮拔。等待隊(duì)列中按照FIFO的原則獲取鎖述暂,等待時(shí)間越長的線程越先獲取到鎖,這就是公平的獲取鎖建炫,即公平鎖畦韭。而非公平鎖,線程獲取的鎖的時(shí)候肛跌,無視等待隊(duì)列直接獲取鎖艺配。ReentrantLock和ReentrantReadWriteLock.Writelock是獨(dú)占鎖据过。
  • 共享鎖:同一個(gè)時(shí)候能夠被多個(gè)線程獲取的鎖,能被共享的鎖妒挎。JUC包中ReentrantReadWriteLock.ReadLock绳锅,CyclicBarrier,CountDownLatch和Semaphore都是共享鎖酝掩。

2鳞芙、基于AQS實(shí)現(xiàn)鎖
AQS中沒有實(shí)現(xiàn)任何的同步接口,所以一般子類通過繼承AQS以內(nèi)部類的形式實(shí)現(xiàn)鎖機(jī)制期虾。一般通過繼承AQS類實(shí)現(xiàn)同步器原朝,通過getState、setState镶苞、compareAndSetState來監(jiān)測狀態(tài)喳坠,并重寫以下方法:

  • tryAcquire():獨(dú)占方式。嘗試獲取資源茂蚓,成功則返回true壕鹉,失敗則返回false。
  • tryRelease():獨(dú)占方式聋涨。嘗試釋放資源晾浴,成功則返回true,失敗則返回false牍白。
  • tryAcquireShared():共享方式脊凰。嘗試獲取資源。負(fù)數(shù)表示失斆取狸涌;0表示成功,但沒有剩余可用資源最岗;正數(shù)表示成功帕胆,且有剩余資源。
  • tryReleaseShared():共享方式仑性。嘗試釋放資源惶楼,如果釋放后允許喚醒后續(xù)等待結(jié)點(diǎn)返回true,否則返回false诊杆。
  • isHeldExclusively():該線程是否正在獨(dú)占資源。只有用到condition才需要去實(shí)現(xiàn)它何陆。

一般來說晨汹,自定義同步器要么是獨(dú)占方法,要么是共享方式贷盲,他們也只需實(shí)現(xiàn)tryAcquire-tryRelease淘这、tryAcquireShared-tryReleaseShared中的一種即可剥扣。但AQS也支持自定義同步器同時(shí)實(shí)現(xiàn)獨(dú)占和共享兩種方式,如ReentrantReadWriteLock铝穷。

3钠怯、AQS同步器的存儲(chǔ)結(jié)構(gòu)

二、AQS源碼解析

1曙聂、存儲(chǔ)節(jié)點(diǎn)Node
我們一直在說AQS是基于FIFO隊(duì)列的存儲(chǔ)結(jié)構(gòu)晦炊,它是以內(nèi)部類Node節(jié)點(diǎn)的形式進(jìn)行存儲(chǔ)。這個(gè)等待隊(duì)列是CLH同步隊(duì)列宁脊。

static final class Node {
    /** 共享節(jié)點(diǎn)模式下的節(jié)點(diǎn) */
    static final Node SHARED = new Node();
    /** 獨(dú)占模式下的節(jié)點(diǎn) */
    static final Node EXCLUSIVE = null;

    /** 取消狀態(tài) */
    static final int CANCELLED =  1;
    /** 后繼節(jié)點(diǎn)的線程處于等待狀態(tài)断国,而當(dāng)前節(jié)點(diǎn)的線程如果釋放了同步狀態(tài)或者被取消,將會(huì)通知后繼節(jié)點(diǎn)榆苞,使后繼節(jié)點(diǎn)的線程得以運(yùn)行 */
    static final int SIGNAL    = -1;
    /** waitStatus value to indicate thread is waiting on condition */
    static final int CONDITION = -2;
    /**
     * 下一次共享式同步狀態(tài)獲取將會(huì)無條件地傳播下去
     */
    static final int PROPAGATE = -3;

    /**
     * Status field, taking on only the values:
     *   SIGNAL:     The successor of this node is (or will soon be)
     *               blocked (via park), so the current node must
     *               unpark its successor when it releases or
     *               cancels. To avoid races, acquire methods must
     *               first indicate they need a signal,
     *               then retry the atomic acquire, and then,
     *               on failure, block.
     *   CANCELLED:  This node is cancelled due to timeout or interrupt.
     *               Nodes never leave this state. In particular,
     *               a thread with cancelled node never again blocks.
     *   CONDITION:  This node is currently on a condition queue.
     *               It will not be used as a sync queue node
     *               until transferred, at which time the status
     *               will be set to 0. (Use of this value here has
     *               nothing to do with the other uses of the
     *               field, but simplifies mechanics.)
     *   PROPAGATE:  A releaseShared should be propagated to other
     *               nodes. This is set (for head node only) in
     *               doReleaseShared to ensure propagation
     *               continues, even if other operations have
     *               since intervened.
     *   0:          None of the above
     *
     * The values are arranged numerically to simplify use.
     * Non-negative values mean that a node doesn't need to
     * signal. So, most code doesn't need to check for particular
     * values, just for sign.
     *
     * The field is initialized to 0 for normal sync nodes, and
     * CONDITION for condition nodes.  It is modified using CAS
     * (or when possible, unconditional volatile writes).
     */
    volatile int waitStatus;

    /**
     * 前驅(qū)節(jié)點(diǎn)
     */
    volatile Node prev;

    /**
     * 后驅(qū)節(jié)點(diǎn)
     */
    volatile Node next;

    /**
     * 獲取同步狀態(tài)的線程
     */
    volatile Thread thread;

    /**
     * Link to next node waiting on condition, or the special
     * value SHARED.  Because condition queues are accessed only
     * when holding in exclusive mode, we just need a simple
     * linked queue to hold nodes while they are waiting on
     * conditions. They are then transferred to the queue to
     * re-acquire. And because conditions can only be exclusive,
     * we save a field by using special value to indicate shared
     * mode.
     */
    Node nextWaiter;

    /**
     * Returns true if node is waiting in shared mode.
     */
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    /**
     * Returns previous node, or throws NullPointerException if null.
     * Use when predecessor cannot be null.  The null check could
     * be elided, but is present to help the VM.
     *
     * @return the predecessor of this node
     */
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    Node() {    // Used to establish initial head or SHARED marker
    }

    Node(Thread thread, Node mode) {     // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }

    Node(Thread thread, int waitStatus) { // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

在Node內(nèi)部類中稳衬,聲明了pre、next節(jié)點(diǎn)用于隊(duì)列的連接坐漏,同時(shí)保存了waitStatus狀態(tài)薄疚。

2、AQS源碼解析
在面向?qū)ο蟮氖澜缰猩蘖眨胍私庖粋€(gè)類有什么特點(diǎn)输涕,就要看它的屬性。通過查看源碼慨畸,我們看到AQS類包含:

/**
 * Head of the wait queue, lazily initialized.  Except for
 * initialization, it is modified only via method setHead.  Note:
 * If head exists, its waitStatus is guaranteed not to be
 * CANCELLED.
 */
private transient volatile Node head;

/**
 * Tail of the wait queue, lazily initialized.  Modified only via
 * method enq to add new wait node.
 */
private transient volatile Node tail;

/**
 * The synchronization state.
 */
private volatile int state;
  • 前驅(qū)頭節(jié)點(diǎn)-head
  • 后驅(qū)尾節(jié)點(diǎn)-tail
  • 同步器狀態(tài)-state

AQS基于FIFO隊(duì)列莱坎,接下來就依照acquire-release、acquireShared-releaseShared的次序來分析入隊(duì)和出隊(duì)寸士。

1檐什、acquire(int)
此方法是獨(dú)占模式下線程獲取共享資源的頂層入口。如果獲取到資源成功弱卡,線程直接返回乃正,否則進(jìn)入等待隊(duì)列,直到獲取到資源為止婶博,且整個(gè)過程忽略中斷的影響瓮具。這也正是lock()的語義,當(dāng)然不僅僅只限于lock()凡人。獲取到資源后名党,線程就可以去執(zhí)行其臨界區(qū)代碼了。下面是acquire源碼:

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

在acquire方法中調(diào)用了tryAcquire()挠轴、acquireQueued()传睹、addWaiter()三個(gè)方法。

首先通過tryAcquire方法嘗試申請獨(dú)占鎖岸晦。如果獲取成功則返回true欧啤,否則返回false睛藻。

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

呦西,源碼中直接throw一個(gè)異常邢隧。結(jié)合我們前面自定義鎖的知識(shí)店印,AQS只是一個(gè)框架,具體資源獲取和釋放方式交由自定義同步器實(shí)現(xiàn)倒慧。AQS這里只定義了一個(gè)接口庶香,具體資源的獲取交由自定義同步器去實(shí)現(xiàn)了(通過state的get/set/CAS)V粝浴!!至于能不能重入卑雁,能不能加塞米酬,那就看具體的自定義同步器怎么去設(shè)計(jì)了F锿琛v搿!當(dāng)然盹牧,自定義同步器在進(jìn)行資源訪問時(shí)要考慮線程安全的影響俩垃。

這里之所以沒有定義成abstract,是因?yàn)楠?dú)占模式下只用實(shí)現(xiàn)tryAcquire-tryRelease汰寓,而共享模式下只用實(shí)現(xiàn)tryAcquireShared-tryReleaseShared口柳。如果都定義成abstract,那么每個(gè)模式也要去實(shí)現(xiàn)另一模式下的接口有滑。說到底跃闹,Doug Lea還是站在咱們開發(fā)者的角度,盡量減少不必要的工作量毛好。

接著就是addWaiter()方法用于將當(dāng)前線程添加到等待隊(duì)列的隊(duì)尾望艺,并返回當(dāng)前線程所在的節(jié)點(diǎn)

private Node addWaiter(Node mode) {
    //以給定的Node節(jié)點(diǎn)模式構(gòu)建當(dāng)前線程的Node節(jié)點(diǎn)肌访,在acquire方法中傳入的是EXCLUSIVE獨(dú)占式節(jié)點(diǎn)
    Node node = new Node(Thread.currentThread(), mode);
    // 將尾節(jié)點(diǎn)進(jìn)行保存
    Node pred = tail;
    if (pred != null) {//如果尾節(jié)點(diǎn)不為null
        //將尾節(jié)點(diǎn)設(shè)置尾新節(jié)點(diǎn)的prev節(jié)點(diǎn)
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {//通過CAS保證找默,確保節(jié)點(diǎn)能夠被線程安全的添加
            //將當(dāng)前界定指向前驅(qū)的next節(jié)點(diǎn)
            pred.next = node;
            return node;
        }
    }
    //如果尾節(jié)點(diǎn)為null,則通過enq進(jìn)行入隊(duì)
    enq(node);
    return node;
}

//同步器通過死循環(huán)的方式來保證節(jié)點(diǎn)的正確添加吼驶,在“死循環(huán)” 中通過CAS將節(jié)點(diǎn)設(shè)置成為尾節(jié)點(diǎn)之后惩激,
//當(dāng)前線程才能從該方法中返回,否則當(dāng)前線程不斷的嘗試設(shè)置蟹演。
private Node enq(final Node node) {
    //CAS"自旋"风钻,直到成功加入隊(duì)尾
    for (;;) {
        //尾節(jié)點(diǎn)臨時(shí)存儲(chǔ)
        Node t = tail;
        if (t == null) { // Must initialize
            //如果tail為null,則將
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

在addWaiter(Node node)方法中轨帜,將當(dāng)前線程節(jié)點(diǎn)添加到等待隊(duì)列中魄咕。

enq.png

acquireQueued在隊(duì)列中的線程獲取鎖

/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
* 
* acquireQueued方法當(dāng)前線程在死循環(huán)中獲取同步狀態(tài),而只有前驅(qū)節(jié)點(diǎn)是頭節(jié)點(diǎn)才能嘗試獲取同步狀態(tài)(鎖)( p == head && tryAcquire(arg))
*     原因是:1.頭結(jié)點(diǎn)是成功獲取同步狀態(tài)(鎖)的節(jié)點(diǎn)蚌父,而頭節(jié)點(diǎn)的線程釋放了同步狀態(tài)以后哮兰,將會(huì)喚醒其后繼節(jié)點(diǎn),后繼節(jié)點(diǎn)的線程被喚醒后要檢查自己的前驅(qū)節(jié)點(diǎn)是否為頭結(jié)點(diǎn)苟弛。
*           2.維護(hù)同步隊(duì)列的FIFO原則喝滞,節(jié)點(diǎn)進(jìn)入同步隊(duì)列之后,就進(jìn)入了一個(gè)自旋的過程膏秫,每個(gè)節(jié)點(diǎn)(或者說是每個(gè)線程)都在自省的觀察右遭。
* 
*/
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        //死循環(huán)檢查(自旋檢查)當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)是否為頭結(jié)點(diǎn),才能獲取鎖
        for (;;) {
            // 獲取節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {//節(jié)點(diǎn)中的線程循環(huán)的檢查缤削,自己的前驅(qū)節(jié)點(diǎn)是否為頭節(jié)點(diǎn)
                //將當(dāng)前節(jié)點(diǎn)設(shè)置為頭結(jié)點(diǎn)窘哈,移除之前的頭節(jié)點(diǎn)
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 否則檢查前一個(gè)節(jié)點(diǎn)的狀態(tài),看當(dāng)前獲取鎖失敗的線程是否要掛起
            if (shouldParkAfterFailedAcquire(p, node) &&
                //如果需要掛起亭敢,借助JUC包下面的LockSupport類的靜態(tài)方法park掛起當(dāng)前線程滚婉,直到被喚醒
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        //如果有異常
        if (failed)
            //取消請求,將當(dāng)前節(jié)點(diǎn)從隊(duì)列中移除
            cancelAcquire(node);
    }
}

通過addWaiter方法添加到等待隊(duì)列中后帅刀,在通過acquireQueued方法進(jìn)行鎖的獲取

函數(shù)流程:

  • tryAcquire()嘗試直接去獲取資源让腹,如果成功則直接返回;
  • addWaiter()將該線程加入等待隊(duì)列的尾部扣溺,并標(biāo)記為獨(dú)占模式骇窍;
  • acquireQueued()使線程在等待隊(duì)列中獲取資源,一直獲取到資源后才返回锥余。如果在整個(gè)等待過程中被中斷過腹纳,則返回true,否則返回false驱犹。
  • 如果線程在等待過程中被中斷過嘲恍,它是不響應(yīng)的。只是獲取資源后才再進(jìn)行自我中斷selfInterrupt()着绷,將中斷補(bǔ)上蛔钙。

獨(dú)占式鎖獲取流程
調(diào)用同步器的acquire(int arg)方法可以獲取同步狀態(tài),該方法對中斷不敏感荠医,即線程獲取同步狀態(tài)失敗后進(jìn)入同步隊(duì)列吁脱,后續(xù)對線程進(jìn)行中斷操作時(shí),線程不會(huì)從同步隊(duì)列中移除彬向。獲取流程:

  1. 當(dāng)前線程通過tryAcquire()方法嘗試獲取鎖兼贡,成功則直接返回,失敗則進(jìn)入隊(duì)列排隊(duì)等待娃胆,通過CAS獲取同步狀態(tài)遍希。
  2. 如果嘗試獲取鎖失敗的話,構(gòu)造同步節(jié)點(diǎn)(獨(dú)占式的Node.EXCLUSIVE)里烦,通過addWaiter(Node node,int args)方法,將節(jié)點(diǎn)加入到同步隊(duì)列的隊(duì)列尾部凿蒜。
  3. 最后調(diào)用acquireQueued(final Node node, int args)方法禁谦,使該節(jié)點(diǎn)以死循環(huán)的方式獲取同步狀態(tài),如果獲取不到废封,則阻塞節(jié)點(diǎn)中的線程州泊。acquireQueued方法當(dāng)前線程在死循環(huán)中獲取同步狀態(tài),而只有前驅(qū)節(jié)點(diǎn)是頭節(jié)點(diǎn)的時(shí)候才能嘗試獲取鎖(同步狀態(tài))( p == head && tryAcquire(arg))漂洋。
lock.png

2遥皂、release(int):獨(dú)占鎖的釋放
在AQS中通過release方法進(jìn)行鎖的釋放。

public final boolean release(int arg) {
   //調(diào)用tryRelease方法釋放
    if (tryRelease(arg)) {//如果釋放成功
        Node h = head;
        //如果頭節(jié)點(diǎn)不為null刽漂,并且頭結(jié)點(diǎn)的waitStatus值不為0演训,即有狀態(tài)
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
   //沒有釋放成功返回false
    return false;
}

// tryRelease() 嘗試釋放當(dāng)前線程的同步狀態(tài)(鎖)
protected final boolean tryRelease(int releases) {
        //c為釋放后的同步狀態(tài)
      int c = getState() - releases;
      //判斷當(dāng)前釋放鎖的線程是否為獲取到鎖(同步狀態(tài))的線程,不是拋出異常(非法監(jiān)視器狀態(tài)異常)
      if (Thread.currentThread() != getExclusiveOwnerThread())
          throw new IllegalMonitorStateException();
      boolean free = false;
      //如果鎖(同步狀態(tài))已經(jīng)被當(dāng)前線程徹底釋放贝咙,則設(shè)置鎖的持有者為null样悟,同步狀態(tài)(鎖)變的可獲取
      if (c == 0) {
          free = true;
          setExclusiveOwnerThread(null);
      }
      setState(c);
      return free;
  }

private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    Node s = node.next;
    //從隊(duì)列尾部開始往前去找最前面的一個(gè)waitStatus小于0的節(jié)點(diǎn)。
    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)對應(yīng)的線程
    if (s != null)
        LockSupport.unpark(s.thread);
}

release()是獨(dú)占模式下線程釋放共享資源的頂層入口颈畸。它會(huì)釋放指定量的資源乌奇,如果徹底釋放了(即state=0),它會(huì)喚醒等待隊(duì)列里的其他線程來獲取資源。

3眯娱、acquireShared(int)
此方法是共享模式下線程獲取共享資源的頂層入口礁苗。它會(huì)獲取指定量的資源,獲取成功則直接返回徙缴,獲取失敗則進(jìn)入等待隊(duì)列试伙,直到獲取到資源為止,整個(gè)過程忽略中斷于样。下面是acquireShared()的源碼:

public final void acquireShared(int arg) {
     if (tryAcquireShared(arg) < 0)
         doAcquireShared(arg);
}

private void doAcquireShared(int arg) {
    final Node node = addWaiter(Node.SHARED);//加入隊(duì)列尾部
    boolean failed = true;//是否成功標(biāo)志
    try {
        boolean interrupted = false;//等待過程中是否被中斷過的標(biāo)志
        for (;;) {
            final Node p = node.predecessor();//前驅(qū)
            if (p == head) {//如果到head的下一個(gè)疏叨,因?yàn)閔ead是拿到資源的線程,此時(shí)node被喚醒穿剖,很可能是head用完資源來喚醒自己的
                int r = tryAcquireShared(arg);//嘗試獲取資源
                if (r >= 0) {//成功
                    setHeadAndPropagate(node, r);//將head指向自己蚤蔓,還有剩余資源可以再喚醒之后的線程
                    p.next = null; // help GC
                    if (interrupted)//如果等待過程中被打斷過,此時(shí)將中斷補(bǔ)上糊余。
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            
            //判斷狀態(tài)秀又,尋找安全點(diǎn),進(jìn)入waiting狀態(tài)贬芥,等著被unpark()或interrupt()
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

這里tryAcquireShared()依然需要自定義同步器去實(shí)現(xiàn)吐辙。但是AQS已經(jīng)把其返回值的語義定義好了:負(fù)值代表獲取失敗蘸劈;0代表獲取成功昏苏,但沒有剩余資源;正數(shù)表示獲取成功,還有剩余資源贤惯,其他線程還可以去獲取洼专。所以這里acquireShared()的流程就是:

  1. tryAcquireShared()嘗試獲取資源,成功則直接返回救巷;
  2. 失敗則通過doAcquireShared()進(jìn)入等待隊(duì)列park()壶熏,直到被unpark()/interrupt()并成功獲取到資源才返回句柠。整個(gè)等待過程也是忽略中斷的浦译。

doAcquireShared(int)此方法用于將當(dāng)前線程加入等待隊(duì)列尾部休息,直到其他線程釋放資源喚醒自己溯职,自己成功拿到相應(yīng)量的資源后才返回精盅。

4、releaseShared()
releaseShared()是共享模式下線程釋放共享資源的頂層入口谜酒。它會(huì)釋放指定量的資源叹俏,如果成功釋放且允許喚醒等待線程,它會(huì)喚醒等待隊(duì)列里的其他線程來獲取資源僻族。

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {//嘗試釋放資源
        doReleaseShared();//喚醒后繼結(jié)點(diǎn)
        return true;
    }
    return false;
}

此方法的流程也比較簡單粘驰,一句話:釋放掉資源后,喚醒后繼述么。跟獨(dú)占模式下的release()相似蝌数,但有一點(diǎn)稍微需要注意:獨(dú)占模式下的tryRelease()在完全釋放掉資源(state=0)后,才會(huì)返回true去喚醒其他線程度秘,這主要是基于獨(dú)占下可重入的考量顶伞;而共享模式下的releaseShared()則沒有這種要求,共享模式實(shí)質(zhì)就是控制一定量的線程并發(fā)執(zhí)行剑梳,那么擁有資源的線程在釋放掉部分資源時(shí)就可以喚醒后繼等待結(jié)點(diǎn)唆貌。例如,資源總量是13垢乙,A(5)和B(7)分別獲取到資源并發(fā)運(yùn)行锨咙,C(4)來時(shí)只剩1個(gè)資源就需要等待。A在運(yùn)行過程中釋放掉2個(gè)資源量追逮,然后tryReleaseShared(2)返回true喚醒C酪刀,C一看只有3個(gè)仍不夠繼續(xù)等待;隨后B又釋放2個(gè)羊壹,tryReleaseShared(2)返回true喚醒C蓖宦,C一看有5個(gè)夠自己用了,然后C就可以跟A和B一起運(yùn)行油猫。而ReentrantReadWriteLock讀鎖的tryReleaseShared()只有在完全釋放掉資源(state=0)才返回true稠茂,所以自定義同步器可以根據(jù)需要決定tryReleaseShared()的返回值。

總結(jié)
本節(jié)中針對AQS進(jìn)行了簡單的學(xué)習(xí),總結(jié)中參照了以下文章睬关,推薦大家去閱讀下诱担。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市电爹,隨后出現(xiàn)的幾起案子蔫仙,更是在濱河造成了極大的恐慌,老刑警劉巖丐箩,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摇邦,死亡現(xiàn)場離奇詭異,居然都是意外死亡屎勘,警方通過查閱死者的電腦和手機(jī)施籍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來概漱,“玉大人丑慎,你說我怎么就攤上這事∪看荩” “怎么了竿裂?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長照弥。 經(jīng)常有香客問我腻异,道長,這世上最難降的妖魔是什么产喉? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任捂掰,我火速辦了婚禮,結(jié)果婚禮上曾沈,老公的妹妹穿的比我還像新娘这嚣。我一直安慰自己,他們只是感情好塞俱,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布姐帚。 她就那樣靜靜地躺著,像睡著了一般障涯。 火紅的嫁衣襯著肌膚如雪罐旗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天唯蝶,我揣著相機(jī)與錄音九秀,去河邊找鬼。 笑死粘我,一個(gè)胖子當(dāng)著我的面吹牛鼓蜒,可吹牛的內(nèi)容都是我干的痹换。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼都弹,長吁一口氣:“原來是場噩夢啊……” “哼娇豫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起畅厢,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤冯痢,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后框杜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體浦楣,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年霸琴,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了椒振。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,117評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡梧乘,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出庐杨,到底是詐尸還是另有隱情选调,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布灵份,位于F島的核電站仁堪,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏填渠。R本人自食惡果不足惜弦聂,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望氛什。 院中可真熱鬧莺葫,春花似錦、人聲如沸枪眉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贸铜。三九已至堡纬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蒿秦,已是汗流浹背烤镐。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留棍鳖,地道東北人炮叶。 一個(gè)月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親悴灵。 傳聞我的和親對象是個(gè)殘疾皇子扛芽,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評論 2 345

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