1. 程式人生 > 其它 >ReentrantLock原理及原始碼閱讀

ReentrantLock原理及原始碼閱讀

ReentrantLock原理及原始碼閱讀

1、ReentrantLock介紹

ReentrantLock是可重入的獨佔鎖,同時只能有一個執行緒可以獲取到該鎖,其他執行緒獲取該鎖的執行緒將會被阻塞而被放入該鎖的AQS阻塞佇列裡面。

ReentrantLock最終還是使用AQS來實現的,並且根據引數來決定其內部是一個公平還是非公平鎖。

public ReentrantLock() {
    sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = -5179523762034025860L;

    /**
     * Performs {@link Lock#lock}. The main reason for subclassing
     * is to allow fast path for nonfair version.
     */
    abstract void lock();
    ....
}

其中Sync類直接繼承自AQS,它的子類NonfairSyncFairSync分別實現了獲取鎖的公平和非公平策略

2、獲取鎖的方法

void lock()方法

當一個執行緒呼叫該方法時,說明該執行緒希望獲取該鎖,如果鎖當前沒有被其他執行緒佔用,並且當前執行緒之前沒有獲取過該鎖,則當前執行緒會獲取到該鎖,然後設定當前鎖的擁有者為當前執行緒,並設定AQS的狀態值為1,然後直接返回。如果當前執行緒之前已經獲取過該鎖,則這次只是簡單的把AQS的狀態值加1後返回。如果該鎖寂靜被其他執行緒持有,則呼叫該方法的執行緒會被放入AQS佇列後阻塞掛起。

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

ReentranLock的lock委託給了sync類,根據建立ReentranLock的建構函式選擇sync的實現是非公平還是公平,這個鎖是一個非公平鎖或公平鎖。以下是sync的子類NonfairSync的情況,也就是非公平鎖。

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
        // CAS 設定狀態值state
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            // 獲取失敗則呼叫AQS的acquire方法
            acquire(1);
    }

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

上述程式碼中,預設AQS狀態值state為0,所以此一個呼叫lock的執行緒會通過CAS設定狀態值為1,CAS成功則代表當前執行緒獲取到了鎖,然後通過setExclusiveOwnerThread設定該鎖的持有者為當前執行緒。

protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}

如果有其他執行緒呼叫lock方法獲取該鎖,CAS會失敗,然後呼叫AQS的acquire方法,傳遞的引數為1.

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

AQS並沒有實現tryAcquire方法,tryAcquire方法需要子類自己定製化,所以上面的tryAcquire實際上會呼叫ReentrantLock重寫的tryAcquire方法。

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    // 獲取當前狀態值。
    int c = getState();
    // 當前狀態值為0則通過CAS獲得鎖
    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;
    }
    //  為拿到鎖返回false,然後該執行緒被放入AQS阻塞佇列。
    return false;
}

非公平鎖的體現:假設執行緒1呼叫lock方法時發現state狀態值不為0,且當前執行緒不是鎖的持有者則返回false,然後當前執行緒被放入AQS阻塞佇列。這時執行緒2也呼叫了lock方法,發現當前狀態值為0了(假設佔有該鎖的其他執行緒釋放了該鎖),所以通過CAS設定獲取到了該鎖。明明是執行緒1先請求獲取的該鎖,然而執行緒2獲取到了鎖,這就是非公平的體現。這裡執行緒2獲取鎖前並沒有檢視當前AQS佇列裡面是否有比自己更早請求該鎖的執行緒,而是使用了搶奪策略。

protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 當前state為0.
    if (c == 0) {
        // 公平性策略,判斷阻塞佇列裡面是否有其他執行緒
        if (!hasQueuedPredecessors() &&
            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;
}

上述程式碼為公平鎖的方法,公平鎖的話只需要看FairSync重寫的tryAcquire方法。

public final boolean hasQueuedPredecessors() {
    // The correctness of this depends on head being initialized
    // before tail and on head.next being accurate if the current
    // thread is first in queue.
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

上面程式碼中,是實現公平性的核心程式碼,如果當前AQS佇列為空或者當前執行緒是AQS的第一個節點則返回false。其中如果ht則說明當前佇列為空,直接返回false。如果h!=t且snull則說明有一個元素將要作為AQS的第一個節點入佇列,那麼返回true,如果h!=t並且s!=null和s.thread !=Thread.currentThread則說明佇列裡面的第一個元素不是當前執行緒,那麼返回true。

3、釋放鎖的方法

void unlock()方法
public void unlock() {
    sync.release(1);
}

嘗試釋放鎖,如果當前執行緒持有該鎖,則呼叫該方法會讓該執行緒對該執行緒持有的AQS狀態值減1,如果減去1後當前狀態值為0,則當前執行緒會釋放鎖,否則僅僅只是減1而已。如果當前執行緒沒有持有該鎖而呼叫了該方法則會丟擲異常。

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    // 如果不是鎖的持有者,則丟擲異常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 如果當前可重入次數為0, 則清空鎖持有執行緒
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 設定可重入次數為原始值-1
    setState(c);
    return free;
}