可重入的獨佔鎖——ReentrantLock源碼分析

ReentrantLock面試題分析

一、ReentrantLock是怎麼實現的?node

二、ReentrantLock的公平鎖和非公平鎖是如何實現的?面試

1.ReentrantLock類圖結構

從類圖咱們能夠直觀地瞭解到,ReentrantLock最終仍是使用AQS來實現地,而且根據參數來決定其內部是一個公平🔒仍是非公平鎖🔒,默認是非公平鎖🔒。編程

public ReentrantLock() {
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

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

若是讀者對AQS還不瞭解的話,能夠去看看個人這篇文章:抽象同步隊列AQS——AbstractQueuedSynchronizer鎖詳解安全

在這裏,AQS的state狀態值表示線程獲取該鎖的可重入次數,在默認狀況下,state的值爲0表示當前鎖沒有被任何線程持有。當一個線程第一次獲取該鎖時,會嘗試使用CAS設置state的值爲1,併發

若是CAS成功則當前線程獲取了該鎖,而後記錄該鎖的持有者爲當前線程。在該線程沒用釋放鎖的狀況下第二次獲取該鎖後,狀態值被設置爲2,這就是可重入次數。函數

在該線程釋放鎖時,會嘗試使用CAS讓狀態值減1,若是減1後狀態值爲0,則當前線程釋放該鎖。ui

2.獲取鎖的主要方法

2.1 void lock()方法

lock()獲取鎖,其實就是把state從0變成n(重入鎖能夠累加)。實際調用的是sync的lock方法,分公平和非公平。this

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

在如上代碼中,ReentrantLock的lock()委託給sync類,根據建立的ReentrantLock構造函數選擇sync的實現是NonfairSync仍是FairSync,先看看sync的子類NonfairSync(非公平鎖🔒)的狀況spa

final void lock() {
    if (compareAndSetState(0, 1))//CAS設置狀態值爲1
        setExclusiveOwnerThread(Thread.currentThread());//設置該鎖的持有者爲當前線程
    else //CAS失敗的話
        acquire(1);//調用AQS的acquire方法,傳遞參數爲1
}

下面是AQS的acquire的核心源碼

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&//調用ReentantLock重寫tryAcquire方法
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//tryAcquire返回false會把當前線程放入AQS阻塞隊列
        selfInterrupt();
}

以前說過,AQS並無提供可用的tryAcquire方法,tryAcquire方法須要子類本身定製化,因此這裏代碼會調用ReentantLock重寫的tryAcquire方法。咱們看下非公平鎖🔒的實現

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {//當前AQS狀態爲0,acquires參數傳遞默認爲1,由於以前CAS失敗,再次獲取鎖
        if (compareAndSetState(0, acquires)) {//CAS設置狀態值爲1
            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;//若是當前線程不是該鎖的持有者,則返回false,而後會放入AQS阻塞隊列
}

結束完非公平鎖🔒的實現代碼,回過頭來看看非公平在這裏是怎麼體現的。首先非公平是說先嚐試獲取鎖的線程並不必定比後嘗試獲取鎖的線程優先獲取鎖🔒。

而是使用了搶奪策略。那麼下面咱們看看公平鎖🔒是怎麼實現公平的。

protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {//當前AQS狀態爲0
            if (!hasQueuedPredecessors() &&//公平性策略,判斷隊列還有沒有其它node,要保證公平
                compareAndSetState(0, acquires)) {//CAS設置狀態
                setExclusiveOwnerThread(current);//設置獲取鎖的線程
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {//若是當前線程是該鎖的持有者
            int nextc = c + acquires;//重入次數+1
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);//從新設置鎖的狀態
            return true;
        }
        return false;
    }
}

如上代碼所示,公平的tryAcquire策略與非公平的相似,不一樣之處在於,代碼在設置CAS操做以前添加了hasQueuedPredecessors()方法,該方法是實現公平性的核心代碼。代碼以下

public final boolean hasQueuedPredecessors() {
   
    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());
}

2.2void lockInterruptibly()方法

該方法與lock()方法相似,不一樣在於對中斷進行響應,若是當前線程在調用該方法時,其它線程調用了當前線程的interrupt()方法,則該線程拋出異常而返回

public void lockInterruptibly() throws InterruptedException {
    sync.acquireInterruptibly(1);
}
public final void acquireInterruptibly(int arg)
        throws InterruptedException {
    if (Thread.interrupted())//若是當前線程被中斷,則直接拋出異常
        throw new InterruptedException();
    if (!tryAcquire(arg))//嘗試獲取資源
        doAcquireInterruptibly(arg);//調用AQS可被中斷的方法
}

2.3 boolean tryLock()方法

嘗試獲取鎖,若是當前鎖沒用被其它線程持有,則當前線程獲取該鎖並返回true,不然返回false。注意,該方法不會引發當前線程阻塞

public boolean tryLock() {
    return sync.nonfairTryAcquire(1);
}
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;
}

如上代碼與非公平鎖的tryAcquire()方法代碼相似,因此tryLock()使用的是非公平策略。

2.4 boolean tryLock(long timeout, TimeUnit unit)方法

嘗試獲取鎖,與tryLock()的不一樣之處在於,它設置了超時時間,若是超時時間到了,沒用獲取到鎖,則返回false,如下是相關代碼

public boolean tryLock(long timeout, TimeUnit unit)
        throws InterruptedException {
    return sync.tryAcquireNanos(1, unit.toNanos(timeout));//調用AQS的tryAcquireNanos方法
}
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    return tryAcquire(arg) ||
        doAcquireNanos(arg, nanosTimeout);
}
private boolean doAcquireNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (nanosTimeout <= 0L)
        return false;
    final long deadline = System.nanoTime() + nanosTimeout;
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L)
                return false;
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

3 釋放鎖相關方法

3.1 void unlock()方法

嘗試獲取鎖,若是當前線程持有鎖,則調用該方法會讓該線程持有的AQS狀態值減1,若是減1後當前狀態值爲0,則當前線程會釋放該鎖,不然僅僅減1而已。

若是當前線程沒用持有該鎖而調用了該方法則會拋出異常,代碼以下:

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

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;//AQS狀態值減1
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {//若是當前可重入次數爲0,則清空鎖持有線程
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);//設置可重入次數爲原始值減1
    return free;
}

4.案例介紹

下面使用ReentrantLock來實現一個簡單的線程安全的list集合

public class ReentrantLockList {
    //線程不安全的list
    private ArrayList<String>arrayList=new ArrayList<>();
    //獨佔鎖
    private volatile ReentrantLock lock=new ReentrantLock();

    //添加元素
    public void add(String e){
        lock.lock();
        try {
            arrayList.add(e);
        }finally {
            lock.unlock();
        }
    }
    //刪除元素
    public void remove(String e){
        lock.lock();
        try {
            arrayList.remove(e);
        }finally {
            lock.unlock();
        }
    }
    //獲取數據
    public String get(int index){
        lock.lock();
        try {
            return arrayList.get(index);
        }finally {
            lock.unlock();
        }
    }
}

如上代碼在操做arrayList元素前進行加鎖保證同一時間只有一個線程可用對arrayList數組進行修改,可是也只能一個線程對arrayList進行訪問。

如圖,假如線程Thread-1,Thread-2,Thread-3同時嘗試獲取獨佔鎖ReentrantLock,加上Thread-1獲取到了🔒,則Thread-2和Thread-3就會被轉換爲Node節點並放入ReentrantLock對應的AQS阻塞隊列,然後阻塞掛起。

如圖,假設Thread-1獲取鎖後調用了對應的鎖建立的條件變量1,那麼Thread-1就會釋放獲取到的🔒,而後當前線程就會被轉換爲Node節點插入條件變量1的條件隊列。因爲Thread-1釋放了🔒,因此阻塞到AQS隊列裏面的

Thread-2和Thread-3就會有機會獲取到該鎖,假如使用的是公平性策略,那麼者時候Thread-2會獲取到鎖,從而從AQS隊列裏面移除Thread-2對應的Node節點。

小結:

本章介紹了ReentrantLock的實現原理,ReentrantLock的底層使用AQS實現的可重入獨佔鎖。在這裏AQS狀態值爲0表示當前🔒空閒,爲大於1的值則說明該🔒已經被佔用了。

該🔒內部有公平與非公平實現,默認狀況下是非公平的實現,另外,因爲該鎖的獨佔鎖,因此某一時刻只有一個線程能夠獲取到該🔒。

本文參考書籍

Java併發編程之美

相關文章
相關標籤/搜索