并發(fā)包同步器的核心AQS-深入-加鎖

AQS API
節(jié)點的狀態(tài)

下面我們借助上面的API圖閱讀源碼

獨占模式獲取鎖acquire,忽略中斷
 public final void acquire(int arg) {

        // 嘗試獲取鎖,如果獲取不成功,則加入到CLH隊列等待,并且中斷當(dāng)前線程
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

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

  private Node addWaiter(Node mode) {
        // 用當(dāng)前線程創(chuàng)建一個Node,mode=Node.EXCLUSIVE也就是null(獨占鎖的nextWaiter是null)
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        // 如果CLH隊尾是空的,或者CAS把新節(jié)點更新成尾結(jié)點失敗了,則調(diào)用完整入隊方法enq
        // 否則快速入隊
        Node pred = tail;
        if (pred != null) {
            // 新節(jié)點的前置結(jié)點指向pred(原隊尾結(jié)點)
            node.prev = pred;
            // 將tail更新成新節(jié)點node,但是不會影響局部變量pred,pred還是原隊尾結(jié)點
            if (compareAndSetTail(pred, node)) {
                // 原隊尾結(jié)點.next指向新節(jié)點
                pred.next = node;
                return node;
            }
        }
        // 完整入隊
        enq(node);
        return node;
    }
addWaiter

tryAcquire是需要子類必須重寫的方法,如果不重寫就會拋出UnsupportedOperationException

獨占模式獲取鎖acquire,響應(yīng)中斷
 public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        // 當(dāng)前線程被中斷,拋出InterruptedException
        if (Thread.interrupted())
            throw new InterruptedException();
       // 如果嘗試獲取鎖失敗,那么在可中斷模式下獲取鎖
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

 /**
     * Acquires in exclusive interruptible mode.
     * @param arg the acquire argument
     */
    private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        // 創(chuàng)建一個和當(dāng)前線程綁定Node,并加入隊尾
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                // 獲取node的前置節(jié)點
                final Node p = node.predecessor();
                // 如果node的前置節(jié)點就是頭節(jié)點了,那么應(yīng)該拿到資源的線程應(yīng)該是當(dāng)前線程,也就是新創(chuàng)建的node
                if (p == head && tryAcquire(arg)) {
                    // 拿到資源后把node設(shè)置為head節(jié)點,因為這個node拿到資源了,要輪到下一個node了
                    setHead(node);
                   // 把原h(huán)ead節(jié)點刪除
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                // 如果拿鎖失敗,并且需要掛起,那么需要掛起當(dāng)前線程,并設(shè)置中斷狀態(tài)
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            // 沒拿到鎖取消當(dāng)前節(jié)點拿鎖請求
            if (failed)
                cancelAcquire(node);
        }
    }



private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;

        // 如果前一個節(jié)點的狀態(tài)是SIGNAL,返回true,可以將當(dāng)前線程掛起
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;

        // 如果如果前一個節(jié)點的狀態(tài)是CANCELLED,那么循環(huán)向前查找取消節(jié)點,把取消節(jié)點從隊列中刪除
        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 {

          // 如果是其他狀態(tài),那么將前置節(jié)點的waitStatus設(shè)置成SIGNAL
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

Q:在shouldParkAfterFailedAcquire中為什么前置節(jié)點的狀態(tài)是SIGNAL了,還要掛起當(dāng)前線程?
A:節(jié)點狀態(tài)是SIGNAL表示當(dāng)前節(jié)點如果釋放鎖,可以通知喚醒后面的節(jié)點了。當(dāng)獲取鎖失敗的時候,如果前置節(jié)點狀態(tài)是SIGNAL就表明,可以安全的掛起當(dāng)前線程,因為前置節(jié)點一旦釋放鎖,會通知喚醒后面的節(jié)點。
Q:HEAD節(jié)點是干嘛的,為什要有它?
A:HEAD節(jié)點表示已經(jīng)獲取到鎖的節(jié)點(doAcquireInterruptibly方法,setHead),前面說了,SIGNAL表示當(dāng)前節(jié)點如果釋放鎖,可以通知喚醒后面的節(jié)點了。所以所有的線程在休眠之前都要,把前置節(jié)點的狀態(tài)設(shè)置成SIGNAL,否則自己永遠(yuǎn)無法被喚醒。那么第一個節(jié)點沒有前置節(jié)點怎么辦呢,那就加一個HEAD。

獨占模式獲取鎖tryAcquireNanos(int arg, long nanosTimeout),響應(yīng)中斷并且?guī)в谐瑫r時間
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
       // 如果當(dāng)前線程被中斷那么拋出InterruptedException
        if (Thread.interrupted())
            throw new InterruptedException();
        // 直接獲取鎖,若獲取不成功,就進(jìn)入到帶有超時時間的獲取鎖方法doAcquireNanos
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }

 private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        // 邊界檢查
        if (nanosTimeout <= 0L)
            return false;
        // 當(dāng)前時間+超時時間=過期時間
        final long deadline = System.nanoTime() + nanosTimeout;
        // 新加一個節(jié)點
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                // 若前置節(jié)點是head,說明當(dāng)前線程可以獲取鎖了,獲取鎖成功后,釋放原h(huán)ead節(jié)點
                // 并且將新加的節(jié)點node設(shè)置為head
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                // 循環(huán)拿鎖中超時檢查,若超時返回false
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
                // 如果當(dāng)前節(jié)點可以被掛起,并且還剩余的時間大于超時時間的閾值1000L的話,掛起當(dāng)前線程nanosTimeout納秒
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                  // 如果當(dāng)前線程被中斷了,那么拋出InterruptedException
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {

            // 沒獲取到鎖,取消節(jié)點拿鎖請求
            if (failed)
                cancelAcquire(node);
        }
    }

Q:為何循壞拿鎖的時候,如果當(dāng)前節(jié)點可以被掛起,并且還剩余的時間大于超時時間的閾值1000L的話,掛起當(dāng)前線程nanosTimeout納秒?
A:為了提高響應(yīng)能力,如果不掛起當(dāng)前線程的話,在這剩余的超時時間里會占用資源一直循環(huán)拿鎖,直到超時或者線程被中斷為止。掛起當(dāng)前線程nanosTimeout納秒,在這期間可以讓別的線程先去占資源。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容