Java技術(shù)之AQS詳解

??AQS是AbstractQueuedSynchronizer的簡稱。AQS提供了一種實現(xiàn)阻塞鎖和一系列依賴FIFO等待隊列的同步器的框架诗良,如下圖所示。AQS為一系列同步器依賴于一個單獨(dú)的原子變量(state)的同步器提供了一個非常有用的基礎(chǔ)秆撮。子類們必須定義改變state變量的protected方法捣辆,這些方法定義了state是如何被獲取或釋放的。鑒于此琅摩,本類中的其他方法執(zhí)行所有的排隊和阻塞機(jī)制铁孵。子類也可以維護(hù)其他的state變量,但是為了保證同步房资,必須原子地操作這些變量蜕劝。

AQS.png

?? AbstractQueuedSynchronizer中對state的操作是原子的,且不能被繼承轰异。所有的同步機(jī)制的實現(xiàn)均依賴于對改變量的原子操作岖沛。為了實現(xiàn)不同的同步機(jī)制,我們需要創(chuàng)建一個非共有的(non-public internal)擴(kuò)展了AQS類的內(nèi)部輔助類來實現(xiàn)相應(yīng)的同步邏輯搭独。AbstractQueuedSynchronizer并不實現(xiàn)任何同步接口婴削,它提供了一些可以被具體實現(xiàn)類直接調(diào)用的一些原子操作方法來重寫相應(yīng)的同步邏輯。AQS同時提供了互斥模式(exclusive)和共享模式(shared)兩種不同的同步邏輯牙肝。一般情況下唉俗,子類只需要根據(jù)需求實現(xiàn)其中一種模式,當(dāng)然也有同時實現(xiàn)兩種模式的同步類配椭,如ReadWriteLock虫溜。接下來將詳細(xì)介紹AbstractQueuedSynchronizer的提供的一些具體實現(xiàn)方法。

state狀態(tài)

??AbstractQueuedSynchronizer維護(hù)了一個volatile int類型的變量股缸,用戶表示當(dāng)前同步狀態(tài)衡楞。volatile雖然不能保證操作的原子性,但是保證了當(dāng)前變量state的可見性敦姻。至于volatile的具體語義瘾境,可以參考相關(guān)文章坎背。state的訪問方式有三種:

  • getState()
  • setState()
  • compareAndSetState()

??這三種叫做均是原子操作,其中compareAndSetState的實現(xiàn)依賴于Unsafe的compareAndSwapInt()方法寄雀。代碼實現(xiàn)如下:

    /**
     * The synchronization state.
     */
    private volatile int state;
  
    /**
     * Returns the current value of synchronization state.
     * This operation has memory semantics of a {@code volatile} read.
     * @return current state value
     */
    protected final int getState() {
        return state;
    }

    /**
     * Sets the value of synchronization state.
     * This operation has memory semantics of a {@code volatile} write.
     * @param newState the new state value
     */
    protected final void setState(int newState) {
        state = newState;
    }

    /**
     * Atomically sets synchronization state to the given updated
     * value if the current state value equals the expected value.
     * This operation has memory semantics of a {@code volatile} read
     * and write.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that the actual
     *         value was not equal to the expected value.
     */
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

自定義資源共享方式

??AQS定義兩種資源共享方式:Exclusive(獨(dú)占,只有一個線程能執(zhí)行陨献,如ReentrantLock)和Share(共享盒犹,多個線程可同時執(zhí)行,如Semaphore/CountDownLatch)眨业。
??不同的自定義同步器爭用共享資源的方式也不同急膀。自定義同步器在實現(xiàn)時只需要實現(xiàn)共享資源state的獲取與釋放方式即可,至于具體線程等待隊列的維護(hù)(如獲取資源失敗入隊/喚醒出隊等)龄捡,AQS已經(jīng)在頂層實現(xiàn)好了卓嫂。自定義同步器實現(xiàn)時主要實現(xiàn)以下幾種方法:

  • isHeldExclusively():該線程是否正在獨(dú)占資源。只有用到condition才需要去實現(xiàn)它聘殖。
  • tryAcquire(int):獨(dú)占方式晨雳。嘗試獲取資源,成功則返回true奸腺,失敗則返回false餐禁。
  • tryRelease(int):獨(dú)占方式。嘗試釋放資源突照,成功則返回true帮非,失敗則返回false。
  • tryAcquireShared(int):共享方式讹蘑。嘗試獲取資源末盔。負(fù)數(shù)表示失敗座慰;0表示成功陨舱,但沒有剩余可用資源;正數(shù)表示成功版仔,且有剩余資源隅忿。
  • tryReleaseShared(int):共享方式。嘗試釋放資源邦尊,如果釋放后允許喚醒后續(xù)等待結(jié)點(diǎn)返回true背桐,否則返回false。

源碼實現(xiàn)

??接下來我們開始開始講解AQS的源碼實現(xiàn)蝉揍。依照acquire-release链峭、acquireShared-releaseShared的次序來。

1. acquire(int)

? ? acquire是一種以獨(dú)占方式獲取資源又沾,如果獲取到資源弊仪,線程直接返回熙卡,否則進(jìn)入等待隊列,直到獲取到資源為止励饵,且整個過程忽略中斷的影響驳癌。該方法是獨(dú)占模式下線程獲取共享資源的頂層入口。獲取到資源后役听,線程就可以去執(zhí)行其臨界區(qū)代碼了颓鲜。下面是acquire()的源碼:

/**
     * Acquires in exclusive mode, ignoring interrupts.  Implemented
     * by invoking at least once {@link #tryAcquire},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquire} until success.  This method can be used
     * to implement method {@link Lock#lock}.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquire} but is otherwise uninterpreted and
     *        can represent anything you like.
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

??通過注釋我們知道,acquire方法是一種互斥模式典予,且忽略中斷甜滨。該方法至少執(zhí)行一次tryAcquire(int)方法,如果tryAcquire(int)方法返回true瘤袖,則acquire直接返回衣摩,否則當(dāng)前線程需要進(jìn)入隊列進(jìn)行排隊。函數(shù)流程如下:

  1. tryAcquire()嘗試直接去獲取資源捂敌,如果成功則直接返回艾扮;
  2. addWaiter()將該線程加入等待隊列的尾部,并標(biāo)記為獨(dú)占模式占婉;
  3. acquireQueued()使線程在等待隊列中獲取資源栏渺,一直獲取到資源后才返回。如果在整個等待過程中被中斷過锐涯,則返回true磕诊,否則返回false。
  4. 如果線程在等待過程中被中斷過纹腌,它是不響應(yīng)的霎终。只是獲取資源后才再進(jìn)行自我中斷selfInterrupt(),將中斷補(bǔ)上升薯。

接下來一次介紹相關(guān)方法莱褒。

1.1 tryAcquire(int)

?? tryAcquire嘗試以獨(dú)占的方式獲取資源,如果獲取成功涎劈,則直接返回true广凸,否則直接返回false。該方法可以用于實現(xiàn)Lock中的tryLock()方法蛛枚。該方法的默認(rèn)實現(xiàn)是拋出UnsupportedOperationException谅海,具體實現(xiàn)由自定義的擴(kuò)展了AQS的同步類來實現(xiàn)。AQS在這里只負(fù)責(zé)定義了一個公共的方法框架蹦浦。這里之所以沒有定義成abstract扭吁,是因為獨(dú)占模式下只用實現(xiàn)tryAcquire-tryRelease,而共享模式下只用實現(xiàn)tryAcquireShared-tryReleaseShared。如果都定義成abstract侥袜,那么每個模式也要去實現(xiàn)另一模式下的接口蝌诡。

/**
     * Attempts to acquire in exclusive mode. This method should query
     * if the state of the object permits it to be acquired in the
     * exclusive mode, and if so to acquire it.
     *
     * <p>This method is always invoked by the thread performing
     * acquire.  If this method reports failure, the acquire method
     * may queue the thread, if it is not already queued, until it is
     * signalled by a release from some other thread. This can be used
     * to implement method {@link Lock#tryLock()}.
     *
     * <p>The default
     * implementation throws {@link UnsupportedOperationException}.
     *
     * @param arg the acquire argument. This value is always the one
     *        passed to an acquire method, or is the value saved on entry
     *        to a condition wait.  The value is otherwise uninterpreted
     *        and can represent anything you like.
     * @return {@code true} if successful. Upon success, this object has
     *         been acquired.
     * @throws IllegalMonitorStateException if acquiring would place this
     *         synchronizer in an illegal state. This exception must be
     *         thrown in a consistent fashion for synchronization to work
     *         correctly.
     * @throws UnsupportedOperationException if exclusive mode is not supported
     */
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
1.2 addWaiter(Node)

??該方法用于將當(dāng)前線程根據(jù)不同的模式(Node.EXCLUSIVE互斥模式、Node.SHARED共享模式)加入到等待隊列的隊尾枫吧,并返回當(dāng)前線程所在的結(jié)點(diǎn)浦旱。如果隊列不為空,則以通過compareAndSetTail方法以CAS的方式將當(dāng)前線程節(jié)點(diǎn)加入到等待隊列的末尾九杂。否則颁湖,通過enq(node)方法初始化一個等待隊列,并返回當(dāng)前節(jié)點(diǎn)尼酿。源碼如下:

/**
     * Creates and enqueues node for current thread and given mode.
     *
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new node
     */
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }
1.2.1 enq(node)

??enq(node)用于將當(dāng)前節(jié)點(diǎn)插入等待隊列,如果隊列為空植影,則初始化當(dāng)前隊列裳擎。整個過程以CAS自旋的方式進(jìn)行,直到成功加入隊尾為止思币。源碼如下:

/**
     * Inserts node into queue, initializing if necessary. See picture above.
     * @param node the node to insert
     * @return node's predecessor
     */
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
1.3 acquireQueued(Node, int)

??acquireQueued()用于隊列中的線程自旋地以獨(dú)占且不可中斷的方式獲取同步狀態(tài)(acquire)鹿响,直到拿到鎖之后再返回。該方法的實現(xiàn)分成兩部分:如果當(dāng)前節(jié)點(diǎn)已經(jīng)成為頭結(jié)點(diǎn)谷饿,嘗試獲取鎖(tryAcquire)成功惶我,然后返回;否則檢查當(dāng)前節(jié)點(diǎn)是否應(yīng)該被park博投,然后將該線程park并且檢查當(dāng)前線程是否被可以被中斷绸贡。

/**
     * 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
     */
    final boolean acquireQueued(final Node node, int arg) {
        //標(biāo)記是否成功拿到資源,默認(rèn)false
        boolean failed = true;
        try {
            boolean interrupted = false;//標(biāo)記等待過程中是否被中斷過
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
1.3.1 shouldParkAfterFailedAcquire(Node, Node)

??shouldParkAfterFailedAcquire方法通過對當(dāng)前節(jié)點(diǎn)的前一個節(jié)點(diǎn)的狀態(tài)進(jìn)行判斷毅哗,對當(dāng)前節(jié)點(diǎn)做出不同的操作听怕,至于每個Node的狀態(tài)表示,可以參考接口文檔虑绵。

/**
     * Checks and updates status for a node that failed to acquire.
     * Returns true if thread should block. This is the main signal
     * control in all acquire loops.  Requires that pred == node.prev.
     *
     * @param pred node's predecessor holding status
     * @param node the node
     * @return {@code true} if thread should block
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
1.3.2 parkAndCheckInterrupt()

??該方法讓線程去休息尿瞭,真正進(jìn)入等待狀態(tài)。park()會讓當(dāng)前線程進(jìn)入waiting狀態(tài)翅睛。在此狀態(tài)下声搁,有兩種途徑可以喚醒該線程:1)被unpark();2)被interrupt()捕发。需要注意的是疏旨,Thread.interrupted()會清除當(dāng)前線程的中斷標(biāo)記位。

/**
     * Convenience method to park and then check if interrupted
     *
     * @return {@code true} if interrupted
     */
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

??我們再回到acquireQueued()扎酷,總結(jié)下該函數(shù)的具體流程:

  1. 結(jié)點(diǎn)進(jìn)入隊尾后充石,檢查狀態(tài),找到安全休息點(diǎn);
  2. 調(diào)用park()進(jìn)入waiting狀態(tài)骤铃,等待unpark()或interrupt()喚醒自己拉岁;
  3. 被喚醒后,看自己是不是有資格能拿到號惰爬。如果拿到喊暖,head指向當(dāng)前結(jié)點(diǎn),并返回從入隊到拿到號的整個過程中是否被中斷過撕瞧;如果沒拿到陵叽,繼續(xù)流程1。

最后丛版,總結(jié)一下acquire()的流程:

  1. 調(diào)用自定義同步器的tryAcquire()嘗試直接去獲取資源巩掺,如果成功則直接返回;
  2. 沒成功页畦,則addWaiter()將該線程加入等待隊列的尾部胖替,并標(biāo)記為獨(dú)占模式;
  3. acquireQueued()使線程在等待隊列中休息豫缨,有機(jī)會時(輪到自己独令,會被unpark())會去嘗試獲取資源。獲取到資源后才返回好芭。如果在整個等待過程中被中斷過燃箭,則返回true,否則返回false舍败。
  4. 如果線程在等待過程中被中斷過招狸,它是不響應(yīng)的。只是獲取資源后才再進(jìn)行自我中斷selfInterrupt()邻薯,將中斷補(bǔ)上瓢颅。

2. release(int)

??release(int)方法是獨(dú)占模式下線程釋放共享資源的頂層入口。它會釋放指定量的資源弛说,如果徹底釋放了(即state=0),它會喚醒等待隊列里的其他線程來獲取資源挽懦。這也正是unlock()的語義,當(dāng)然不僅僅只限于unlock()木人。下面是release()的源碼:

/**
     * Releases in exclusive mode.  Implemented by unblocking one or
     * more threads if {@link #tryRelease} returns true.
     * This method can be used to implement method {@link Lock#unlock}.
     *
     * @param arg the release argument.  This value is conveyed to
     *        {@link #tryRelease} but is otherwise uninterpreted and
     *        can represent anything you like.
     * @return the value returned from {@link #tryRelease}
     */
    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

/**
     * Attempts to set the state to reflect a release in exclusive
     * mode.
     *
     * <p>This method is always invoked by the thread performing release.
     *
     * <p>The default implementation throws
     * {@link UnsupportedOperationException}.
     *
     * @param arg the release argument. This value is always the one
     *        passed to a release method, or the current state value upon
     *        entry to a condition wait.  The value is otherwise
     *        uninterpreted and can represent anything you like.
     * @return {@code true} if this object is now in a fully released
     *         state, so that any waiting threads may attempt to acquire;
     *         and {@code false} otherwise.
     * @throws IllegalMonitorStateException if releasing would place this
     *         synchronizer in an illegal state. This exception must be
     *         thrown in a consistent fashion for synchronization to work
     *         correctly.
     * @throws UnsupportedOperationException if exclusive mode is not supported
     */
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

/**
     * Wakes up node's successor, if one exists.
     *
     * @param node the node
     */
    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;
        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;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

??與acquire()方法中的tryAcquire()類似信柿,tryRelease()方法也是需要獨(dú)占模式的自定義同步器去實現(xiàn)的。正常來說醒第,tryRelease()都會成功的渔嚷,因為這是獨(dú)占模式,該線程來釋放資源稠曼,那么它肯定已經(jīng)拿到獨(dú)占資源了形病,直接減掉相應(yīng)量的資源即可(state-=arg),也不需要考慮線程安全的問題。但要注意它的返回值漠吻,上面已經(jīng)提到了量瓜,release()是根據(jù)tryRelease()的返回值來判斷該線程是否已經(jīng)完成釋放掉資源了!所以自義定同步器在實現(xiàn)時途乃,如果已經(jīng)徹底釋放資源(state=0)绍傲,要返回true,否則返回false耍共。
??unparkSuccessor(Node)方法用于喚醒等待隊列中下一個線程烫饼。這里要注意的是,下一個線程并不一定是當(dāng)前節(jié)點(diǎn)的next節(jié)點(diǎn)试读,而是下一個可以用來喚醒的線程杠纵,如果這個節(jié)點(diǎn)存在,調(diào)用unpark()方法喚醒钩骇。
??總之比藻,release()是獨(dú)占模式下線程釋放共享資源的頂層入口。它會釋放指定量的資源伊履,如果徹底釋放了(即state=0),它會喚醒等待隊列里的其他線程來獲取資源韩容。

3. acquireShared(int)

??acquireShared(int)方法是共享模式下線程獲取共享資源的頂層入口款违。它會獲取指定量的資源唐瀑,獲取成功則直接返回,獲取失敗則進(jìn)入等待隊列插爹,直到獲取到資源為止哄辣,整個過程忽略中斷。下面是acquireShared()的源碼:

/**
     * Acquires in shared mode, ignoring interrupts.  Implemented by
     * first invoking at least once {@link #tryAcquireShared},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquireShared} until success.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquireShared} but is otherwise uninterpreted
     *        and can represent anything you like.
     */
    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }


3.1 doAcquireShared(int)

??將當(dāng)前線程加入等待隊列尾部休息赠尾,直到其他線程釋放資源喚醒自己力穗,自己成功拿到相應(yīng)量的資源后才返回。源碼如下:

 /**
     * Acquires in shared uninterruptible mode.
     * @param arg the acquire argument
     */
    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) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

??跟獨(dú)占模式比气嫁,還有一點(diǎn)需要注意的是当窗,這里只有線程是head.next時(“老二”),才會去嘗試獲取資源寸宵,有剩余的話還會喚醒之后的隊友崖面。那么問題就來了,假如老大用完后釋放了5個資源梯影,而老二需要6個巫员,老三需要1個,老四需要2個甲棍。老大先喚醒老二简识,老二一看資源不夠,他是把資源讓給老三呢,還是不讓七扰?答案是否定的奢赂!老二會繼續(xù)park()等待其他線程釋放資源,也更不會去喚醒老三和老四了戳寸。獨(dú)占模式呈驶,同一時刻只有一個線程去執(zhí)行,這樣做未嘗不可疫鹊;但共享模式下袖瞻,多個線程是可以同時執(zhí)行的,現(xiàn)在因為老二的資源需求量大拆吆,而把后面量小的老三和老四也都卡住了聋迎。當(dāng)然,這并不是問題枣耀,只是AQS保證嚴(yán)格按照入隊順序喚醒罷了(保證公平霉晕,但降低了并發(fā))。實現(xiàn)如下:

/**
     * Sets head of queue, and checks if successor may be waiting
     * in shared mode, if so propagating if either propagate > 0 or
     * PROPAGATE status was set.
     *
     * @param node the node
     * @param propagate the return value from a tryAcquireShared
     */
    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        /*
         * Try to signal next queued node if:
         *   Propagation was indicated by caller,
         *     or was recorded (as h.waitStatus either before
         *     or after setHead) by a previous operation
         *     (note: this uses sign-check of waitStatus because
         *      PROPAGATE status may transition to SIGNAL.)
         * and
         *   The next node is waiting in shared mode,
         *     or we don't know, because it appears null
         *
         * The conservatism in both of these checks may cause
         * unnecessary wake-ups, but only when there are multiple
         * racing acquires/releases, so most need signals now or soon
         * anyway.
         */
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                doReleaseShared();
        }
    }

??此方法在setHead()的基礎(chǔ)上多了一步捞奕,就是自己蘇醒的同時牺堰,如果條件符合(比如還有剩余資源),還會去喚醒后繼結(jié)點(diǎn)颅围,畢竟是共享模式伟葫!至此,acquireShared()也要告一段落了院促。讓我們再梳理一下它的流程:

  1. tryAcquireShared()嘗試獲取資源筏养,成功則直接返回;
  2. 失敗則通過doAcquireShared()進(jìn)入等待隊列park()常拓,直到被unpark()/interrupt()并成功獲取到資源才返回渐溶。整個等待過程也是忽略中斷的。

4. releaseShared(int)

??releaseShared(int)方法是共享模式下線程釋放共享資源的頂層入口弄抬。它會釋放指定量的資源茎辐,如果成功釋放且允許喚醒等待線程,它會喚醒等待隊列里的其他線程來獲取資源掂恕。下面是releaseShared()的源碼:

/**
     * Releases in shared mode.  Implemented by unblocking one or more
     * threads if {@link #tryReleaseShared} returns true.
     *
     * @param arg the release argument.  This value is conveyed to
     *        {@link #tryReleaseShared} but is otherwise uninterpreted
     *        and can represent anything you like.
     * @return the value returned from {@link #tryReleaseShared}
     */
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

??此方法的流程也比較簡單拖陆,一句話:釋放掉資源后,喚醒后繼。跟獨(dú)占模式下的release()相似,但有一點(diǎn)稍微需要注意:獨(dú)占模式下的tryRelease()在完全釋放掉資源(state=0)后羔沙,才會返回true去喚醒其他線程钉凌,這主要是基于獨(dú)占下可重入的考量;而共享模式下的releaseShared()則沒有這種要求选调,共享模式實質(zhì)就是控制一定量的線程并發(fā)執(zhí)行骤公,那么擁有資源的線程在釋放掉部分資源時就可以喚醒后繼等待結(jié)點(diǎn)堕义。

/**
     * Release action for shared mode -- signals successor and ensures
     * propagation. (Note: For exclusive mode, release just amounts
     * to calling unparkSuccessor of head if it needs signal.)
     */
    private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in-progress acquires/releases.  This proceeds in the usual
         * way of trying to unparkSuccessor of head if it needs
         * signal. But if it does not, status is set to PROPAGATE to
         * ensure that upon release, propagation continues.
         * Additionally, we must loop in case a new node is added
         * while we are doing this. Also, unlike other uses of
         * unparkSuccessor, we need to know if CAS to reset status
         * fails, if so rechecking.
         */
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

參考:https://www.cnblogs.com/waterystone/p/4920797.html

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末坏瞄,一起剝皮案震驚了整個濱河市桂对,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌鸠匀,老刑警劉巖蕉斜,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異缀棍,居然都是意外死亡宅此,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門爬范,熙熙樓的掌柜王于貴愁眉苦臉地迎上來父腕,“玉大人,你說我怎么就攤上這事青瀑¤盗粒” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵斥难,是天一觀的道長枝嘶。 經(jīng)常有香客問我,道長哑诊,這世上最難降的妖魔是什么群扶? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮搭儒,結(jié)果婚禮上穷当,老公的妹妹穿的比我還像新娘提茁。我一直安慰自己淹禾,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布茴扁。 她就那樣靜靜地躺著铃岔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪峭火。 梳的紋絲不亂的頭發(fā)上毁习,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機(jī)與錄音卖丸,去河邊找鬼纺且。 笑死,一個胖子當(dāng)著我的面吹牛稍浆,可吹牛的內(nèi)容都是我干的载碌。 我是一名探鬼主播猜嘱,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼嫁艇!你這毒婦竟也來了朗伶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤步咪,失蹤者是張志新(化名)和其女友劉穎论皆,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體猾漫,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡点晴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了悯周。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片觉鼻。...
    茶點(diǎn)故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖队橙,靈堂內(nèi)的尸體忽然破棺而出坠陈,到底是詐尸還是另有隱情,我是刑警寧澤捐康,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布仇矾,位于F島的核電站,受9級特大地震影響解总,放射性物質(zhì)發(fā)生泄漏贮匕。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一花枫、第九天 我趴在偏房一處隱蔽的房頂上張望刻盐。 院中可真熱鬧,春花似錦劳翰、人聲如沸敦锌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽乙墙。三九已至,卻和暖如春生均,著一層夾襖步出監(jiān)牢的瞬間听想,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工马胧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留汉买,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓佩脊,卻偏偏與公主長得像蛙粘,于是被迫代替她去往敵國和親朽色。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評論 2 345

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