AQS實現原理

c5p1ng發表於2021-09-03

AQS實現原理

AQS中維護了一個volatile int state(共享資源)和一個CLH佇列。當state=1時代表當前物件鎖已經被佔用,其他執行緒來加鎖時則會失敗,失敗的執行緒被放入一個FIFO的等待佇列中,然後會被UNSAFE.park()操作掛起,等待已經獲得鎖的執行緒釋放鎖才能被喚醒。

我們拿具體場景來分析,假設同時有三個執行緒併發搶佔鎖,此時執行緒一搶佔成功,執行緒二、三搶佔失敗,具體流程如下:

此時AQS內部資料結構為:

上圖可以看到等待佇列中的節點Node是一個雙向連結串列,這裡SIGNAL是Node中waitStatus屬性。

以非公平鎖看下具體實現:

java.util.concurrent.locks.ReentrantLock.NonfairSync:

static final class NonfairSync extends Sync {
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

執行緒進來直接利用CAS嘗試搶佔鎖,如果搶佔成功state值會被修改為1,且設定物件獨佔鎖執行緒為當前執行緒。

執行緒搶佔實現

執行緒二搶佔失敗,執行acquire(1)方法。

java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire()

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

tryAcquire是AbstractQueuedSynchronizer的方法,未提供對應實現,由子類實現:

java.util.concurrent.locks.ReentrantLock .nonfairTryAcquire()

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

nonfairTryAcquire()方法中首先會獲取state的值,如果不為0則說明當前物件的鎖已經被其他執行緒佔有,接著判斷佔有鎖的執行緒是否為當前執行緒,如果是則累加state值,這裡其實就是可重入鎖的具體實現。如果state為0,則執行CAS操作,嘗試更新state值為1,如果更新成功則代表當前執行緒加鎖成功。

當前執行緒二執行tryAcquire()後返回false,接著執行addWaiter(Node.EXCLUSIVE)邏輯,將自己加入到一個FIFO等待佇列中,程式碼實現如下:

java.util.concurrent.locks.AbstractQueuedSynchronizer.addWaiter()

private Node addWaiter(Node mode) {    
    Node node = new Node(Thread.currentThread(), mode);
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

此時佇列中tail指標為空,直接呼叫enq(node)方法將當前執行緒加入等待佇列尾部:

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) {
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

第一次迴圈時tail為空,建立一個哨兵節點,head指向這個哨兵節點;第二次迴圈,將執行緒二對應的node節點掛載到head節點後面並返回當前執行緒建立的節點資訊。繼續往後執行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)邏輯,此時傳入的引數為執行緒二對應的node節點資訊。

java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued()

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        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) &&
                parkAndChecknIterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        return true;
    if (ws > 0) {
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

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

acquireQueued()會先判斷當前傳入的Node對應的前置節點是否為head,如果是則嘗試加鎖。加鎖成功則將當前節點設定為head節點,然後刪除之前的head節點。

如果加鎖失敗或者Node的前置節點不是head節點,就會通過shouldParkAfterFailedAcquire方法將head節點的waitStatus變成SIGNAL=-1,最後執行parkAndChecknIterrupt方法,呼叫LockSupport.park()掛起當前執行緒。此時執行緒二需要等待其他執行緒釋放鎖來喚醒。

執行緒釋放實現

執行緒一執行完後釋放鎖,具體程式碼如下:

java.util.concurrent.locks.AbstractQueuedSynchronizer.release():

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

先執行tryRelease方法,如果執行成功,則繼續判斷head節點的waitStatus是否為0,這個值為SIGNAL=-1不為0,繼續執行unparkSuccessor()方法喚醒head的後置節點。

ReentrantLock.tryRelease():

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

執行完ReentrantLock.tryRelease()後,state被設定為0,Lock物件的獨佔鎖被設定為null。

接著執行java.util.concurrent.locks.AbstractQueuedSynchronizer.unparkSuccessor()方法,喚醒head的後置節點:

private void unparkSuccessor(Node node) {
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    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);
}

這裡主要是將head節點的waitStatus設定為0,然後解除head節點next的指向,使head幾點空置,等待被垃圾回收。

此時重新將head指標指向執行緒二對應的Node節點,且使用LockSupport.unpark方法來喚醒執行緒二。被喚醒的執行緒會接著嘗試獲取鎖,用CAS指令修改state資料。執行完成後AQS中的資料結構如下:

相關文章