A.Q.S源碼分析(exclusive模式下加鎖)

exclusive mode加鎖:

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

1 首先嚐試加鎖,其中tryAcquire需要子類實現。裏面的主要的邏輯是使用CAS把狀態修改爲arg,如果CAS修改成功,那麼標誌加鎖成功。如果失敗,調用addWaiter

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;
    }

2 上面是addWaiter的邏輯,首先把當前線程包裝成一個node,然後嘗試把node設置爲CLH等待隊列的尾部。如果失敗,那麼調用enq,循環CAS,直到成功。可以看到入隊是通過CAS實現的。

3 把當前線程入隊後,調用acquireQueued方法,下面是代碼:

final boolean acquireQueued(final Node node, int arg) {
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } catch (RuntimeException ex) {
            cancelAcquire(node);
            throw ex;
        }
    }
這是A.Q.S的核心邏輯,首先看shouldParhkAfterFailedAcquire方法

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;
    }
如果前繼節點的狀態是signal,那麼前繼節點在release的時候,會喚醒當前節點,那麼可以安全的阻塞了。如果前繼節點是cancelled,那麼需要把這些節點全部刪除。如果前繼節點是0或者PROPAGATE,需要把狀態修改爲signal。返回false的原因是需要再次調用tryAcquire確認是否能拿到鎖。

如果shouldParhkAfterFailedAcquire返回true,那麼parkAndCheckInterrupt會被調用,裏面的邏輯很簡單,調用LockSupport阻塞當前線程:

private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

LockSuppot.park只有在其他線程調用unpark的時候纔會返回。因爲acquire是忽略其他線程的interrupt的,有上層代碼處理。

再返回到acquireQueued方法,檢查前繼節點是否是頭結點(頭結點就是當前拿到鎖的線程的節點),如果是再次嘗試加鎖,如果成功,那麼把頭結點設置爲當前節點。如果失敗,那麼再次阻塞自己。

可以看到acquire的主要邏輯是首先嚐試加鎖,如果加鎖失敗,那麼把線程放到CLH隊列中然後阻塞。其他線程release喚醒後,繼續嘗試加鎖,如果失敗,再次阻塞的這樣一個循環。




發佈了21 篇原創文章 · 獲贊 0 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章