Java并發編程 - 深入剖析ReentrantLock之非公平鎖加鎖流程(第1篇)

這篇文章不是講ReentrantLock的使用,而是通過調試,分析其實現原理。

非公平鎖

測試代碼如下:

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockExample {

    private ReentrantLock lock = new ReentrantLock();

    public void doSomething() {

        lock.lock();

        try {
            System.out.println(Thread.currentThread().getName() + " has been acquired the lock...");
        } finally {
            lock.unlock();
            System.out.println(Thread.currentThread().getName() + " has been released the lock...");
        }

    }

    public static void main(String[] args) {

        ReentrantLockExample lockExample = new ReentrantLockExample();

        Thread A = new Thread(()->{
            lockExample.doSomething();
        }, "thread-A");

        Thread B = new Thread(()->{
            lockExample.doSomething();
        }, "thread-B");

        Thread C = new Thread(()->{
                lockExample.doSomething();
            }, "thread-C");

        Thread D = new Thread(()->{
                lockExample.doSomething();
        }, "thread-D");

        A.start();
        B.start();
        C.start();
        D.start();
    }
}

代碼測試的是4個線程爭搶一把鎖。

1. 加鎖流程解析

通過IDEA多線程調試工具進行調試,調試模擬情景為:A-B-C-D依次獲取鎖,等所有的獲取請求結束,再依次釋放鎖。

第一步:線程A請求鎖

調用ReentrantLock的lock方法:

public void lock() {
    sync.lock();
}

執行到此出,將處理交給sync,我們這里是非公平鎖,則sync為NonfairSync,調用方法:

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
      acquire(1);
}

通過CAS設置state變量,期望當前值是0,更新值是1。state初始值為0,無其他線程更改過,所以這里CAS設置成功,state值變為1。并且exclusiveOwnerThread設置為thread-A對象。

執行圖:

thread-A加鎖.png

lock對象內容:

thread-A加鎖后ReentrantLock對象內容.png

第二步:線程B請求鎖

掛起A線程,執行B線程。

線程B執行到此代碼:

ReentrantLock.java

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

此時state=1,此處CAS執行失敗,接下來執行else語句代碼。

acquire方法在AbstractQueuedSynchronizer類中定義:

AbstractQueuedSynchronizer.java

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

if條件中有兩處,若第一次為false,則第二處會執行。

先來看tryAcquire, tryAcquire在AbstractQueuedSynchronizer重定義,但是沒有定義具體實現,具體實現交給子類,我們這里就是NonfairSync類實現。

NonfairSync

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

nonfairTryAcquire在NonfairSync直接父類也就是Sync中定義:

Sync

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) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

此時state=1,exclusiveOwnerThread=thread-A, current = thread-B。此方法內邏輯不執行,返回false。

返回執行acquireQueued方法。

acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

執行這個方法前要先執行addWaiter方法,此方法用于創建即將入同步隊列的Node(節點)。

AbstractQueueSynchronizer.java

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

此時tail = null,執行enq方法。

AbstractQueueSynchronizer.java

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;
            }
        }
    }
}
  • 第一次循環:創建一個Node對象,設置給head。同時將head賦值給tail,這樣head和tail指向同一個對象。兩者不再為null。
head_tail-0.png
  • 第二次循環:將B節點的前置設置為t(t是中間變量指向的就是head/tail節點),然后將B設置為tail。執行完成后返回t,此時t和head執行同一個對象,其實返回的就是head節點。

執行完成后,head和tail的內容如下:

head_tail-1.png

注意一下,雖然addWaiter方法返回的是頭節點,但是node還是指向的B節點。接著執行acquireQueued方法。

AbstractQueuedSynchronizer.java

 final boolean acquireQueued(final Node node, int arg) {// Node此時是B節點
    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) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}
  • 第一次循環:p為head節點,tryAcquire和前面一樣返回false,第一個if語句不行。緊接著執行shouldParkAfterFailedAcquire方法,方法代碼如下:

AbstractQueuedSynchronzier.java

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

此時pred=head, node = B, ws = 0, 執行:

compareAndSetWaitStatus(pred, ws, Node.SIGNAL);

這個執行完成后,head的waitStatus被設置成Node.SINGAL,這個表示head節點的后繼者也就是B節點代表的線程需要被通知。返回false。

if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
    interrupted = true;

if代碼塊不執行。

  • 第二次循環: p還是head節點,tryAcquire和第一次循環一樣,第一個if依然不執行,繼續執行shouldParkAfterFailedAcquire,此時shouldParkAfterFailedAcquire方法內pre是head節點,而head節點經過第一次循環waitStatus被設置為-1。shouldParkAfterFailedAcquire(p, node)返回true, 則接下來執行parkAndCheckInterrupt()。
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

線程B執行到LockSupport.park(this)處被掛起。

執行圖:

thread-B加鎖.png

第三步:線程C請求鎖

注意:此時線程A是我們主動(通過IDE操作)掛起的(RUNNING),線程B通過上一步的操作被動(park操作)掛起的(WAITTING)。

因為前面已經見到一些邏輯的執行了,這里就不再重復,直接講關系到Node的操作。

通過addWaiter操作,為線程C創建一個Node節點:

AbstractQueuedSynchronzier.java

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

此時tail為B節點,然后將C節點的prev設置為B,通過CAS操作將C節點設置為tail,設置成功將B的next設置為C。然后直接返回C節點。

執行完成后head和tail的內容如下:

head_tail-2.png

addWaiter返回C節點后,執行acquireQueued方法:

AbstractQueuedSynchronzier.java

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) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

此時node=C

  • 第一次循環: C的前置節點是B,這里p=B,第一個if不執行,執行

shouldParkAfterFailedAcquire:

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

pre=B,B的waitStatus=0,則執行compareAndSetWaitStatus(pred, ws, Node.SIGNAL),B的waitStatus被設為-1,表示后繼節點,即C節點所代表的線程需要被喚醒。繼續執行線程C被掛起。

執行圖:

thread-C加鎖.png

第四步:線程D請求鎖

線程D請求鎖和上面的流程幾乎是一樣了,這里就不再分別調試,執行圖:

thread-D加鎖.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。