ReentrantLock 公平鎖原始碼 第2篇

Jame!發表於2022-07-09

Reentrant 2

前兩篇寫完了後我自己研究了下,還有有很多疑惑和問題,這篇就繼續以自問自答的方式寫

如果沒看過第1篇的可以先看看那個https://www.cnblogs.com/sunankang/p/16458795.html

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

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

第一個問題

interrupted這個變數的作用

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

parkAndCheckInterrupt方法中最後return的是這個執行緒是否被打斷,它的作用是啥?

先來回顧interrupt()interrupted()isInterrupted()三者區別,長得很像,注意區分

interrupt()的作用是中斷執行緒,如果被中斷的執行緒處於阻塞狀態下,例如呼叫wait(),join() sleep(),則丟擲異常,否則只是設定一箇中斷標記為true,注意:僅僅是設定中斷狀態為true,並不會去 "中斷" 執行緒

interrupted() 獲取執行緒的中斷狀態並且清空中斷狀態(將中斷狀態設定為false)

isInterrupted() 獲取執行緒的中斷狀態並不會清除中斷狀態

呼叫 interrupt 會使park方法立即結束,可以理解為喚醒

繼續程式碼,看這個變數最後到了哪裡

情況1 沒有被打斷過

假設執行緒沒有被中斷過,那麼parkAndCheckInterrupt返回就是false

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

那麼不進入 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())這個if,獲取到鎖後返回false,回到acquire方法

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

因為false,所以不進入selfInterrupt(),方法結束

情況2 park或準備park,被喚醒後直接獲取到了鎖

先證明一下打斷是會喚醒park中的執行緒的

我就再重複粘一下程式碼了,方便看

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

那麼返回的就是true,回到上級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) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

因為返回true,所以進入if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) 將interrupted返回true

假設迴圈獲取到鎖,那麼再返回上一級acquire()

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

那麼進入selfInterrupt()

static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

是不是有點疑惑?我如果沒有呼叫過interrupt() 那ReentrantLock就不做任何操作,我如果呼叫了,那它再給我呼叫一次 ???? 還有情況3

情況3 park或準備park,被喚醒後沒有獲取到鎖

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;
            }
            //假設在呼叫shouldParkAfterFailedAcquire成功後,馬上就要呼叫parkAndCheckInterrupt 時間片用完了
            if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

那麼這個時候interrupted屬性就有用了

首先要知道一點,一個被中斷的執行緒是無法park的,除非清除了中斷狀態,即設定為將中斷狀態設定為false, 口說無憑,直接上圖

第二張圖還是在park狀態,證明了被打斷的執行緒是無法park的,除非將它中斷狀態設定為false

那麼回到程式碼中就能知道這個的作用

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

如果執行緒被打斷喚醒,還是在for(;;)中,還是去獲取鎖,假設沒有獲取到呢?那麼就一直在for迴圈中嘎嘎跑,因為執行緒的狀態是被中斷的,無法再次park了

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

那麼現在懂了最後的Thread.interrupted()作用了嗎,就是將中斷狀態設定回false,好讓執行緒沒有獲取到鎖繼續park

那這時候可能就問了:那你ReentrantLock把中斷狀態給我清空了,我自己如果有需要根據中斷狀態來判斷的程式碼咋辦啊?

好,我們們從park先被打斷來捋一下

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

因為被打斷,執行緒醒來,執行Thread.interrupted()並清空中斷狀態,返回true

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

因為返回的是true,所以進入if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())的程式碼塊,將interrupted屬性設定為true

那麼for(;;)迴圈再來一次,如果沒有獲取到鎖.繼續park,直到被喚醒,走tryAcquire()獲取到為止,那麼此時interrupted變數就為true了

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

那麼退出acquireQueued()方法回到acquire()中,因為acquireQueued()返回的是true,所以進入selfInterrupt()

static void selfInterrupt() {
    Thread.currentThread().interrupt();
}

所以懂了嗎?

相關文章