可重入鎖-synchronized是可重入鎖嗎?ReentrantLock如何實現(xiàn)可重入的?

前言

????面試題:synchronized是可重入鎖嗎?

????答案:synchronized是可重入鎖。ReentrantLock也是的。



1、什么是可重入鎖呢?

????關(guān)于什么是可重入鎖,我們先來看一段維基百科的定義。

若一個程序或子程序可以“在任意時刻被中斷然后操作系統(tǒng)調(diào)度執(zhí)行另外一段代碼,這段代碼又調(diào)用了該子程序不會出錯”,則稱其為可重入(reentrant或re-entrant)的。即當(dāng)該子程序正在運行時,執(zhí)行線程可以再次進入并執(zhí)行它,仍然獲得符合設(shè)計時預(yù)期的結(jié)果。與多線程并發(fā)執(zhí)行的線程安全不同,可重入強調(diào)對單個線程執(zhí)行時重新進入同一個子程序仍然是安全的。

????通俗來說:當(dāng)線程請求一個由其它線程持有的對象鎖時,該線程會阻塞,而當(dāng)線程請求由自己持有的對象鎖時,如果該鎖是重入鎖,請求就會成功,否則阻塞。

????再換句話說:可重入就是說某個線程已經(jīng)獲得某個鎖,可以再次獲取鎖而不會出現(xiàn)死鎖。



2、自己寫代碼驗證下可重入和不可重入

????我們啟動一個線程t1,調(diào)用addOne()方法來執(zhí)行加1操作。在addOne方法里面t1會獲得rtl鎖,然后調(diào)用get()方法,在get()方法里再次請求獲取trl鎖。

????因為最終能打印value=1,說明t1在第二次獲取鎖的時候并沒有阻塞。說明ReentrantLock是可重入鎖。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ReentrantTest {
    private final Lock rtl = new ReentrantLock();
    int value = 0;

    public static void main(String[] args) throws InterruptedException {
        ReentrantTest test = new ReentrantTest();
        // 新建一個線程 進行加1操作
        Thread t1 = new Thread(() -> test.addOne());
        t1.start();
    // main線程等待t1線程執(zhí)行完
        t1.join();
        System.out.println(test.value);
    }


    public int get() {
        // 獲取鎖
        rtl.lock();
        try {
            return value;
        } finally {
            // 保證鎖能釋放
            rtl.unlock();
        }
    }

    public void addOne() {
        // 獲取鎖
        rtl.lock();
        try {
            value = 1 + get();
        } finally {
            // 保證鎖能釋放
            rtl.unlock();
        }
    }
}

????換成synchronized的加鎖方式,同樣能打印value的值。證明synchronized也是可重入鎖。

public class ReentrantTest {
    private final Object object = new Object();
    int value = 0;

    public static void main(String[] args) throws InterruptedException {
        ReentrantTest test = new ReentrantTest();
        // 新建一個線程 進行加1操作
        Thread t1 = new Thread(() -> test.addOne());
        t1.start();

        t1.join();
        System.out.println(test.value);
    }


    public int get() {
        // 再此獲取鎖
        synchronized (object) {
            return value;
        }
    }

    public void addOne() {
        // 獲取鎖
        synchronized (object) {
            value = 1 + get();
        }
    }
}



3、自己如何實現(xiàn)一個可重入和不可重入鎖呢

不可重入:

public class Lock{
    private boolean isLocked = false;
    public synchronized void lock()
            throws InterruptedException{
        while(isLocked){
            wait();
        }
        isLocked = true;
    }

    public synchronized void unlock(){
        isLocked = false;
        notify();
    }
} 

可重入:

public class Lock{
    boolean isLocked = false;
    Thread  lockedBy = null;
    int lockedCount = 0;
    public synchronized void lock() throws InterruptedException{
        Thread callingThread = Thread.currentThread();
        while(isLocked && lockedBy != callingThread){
            wait();
        }
        isLocked = true;
        lockedCount++;
        lockedBy = callingThread;
    }
    public synchronized void unlock(){
        if(Thread.curentThread() == this.lockedBy){
            lockedCount--;
            if(lockedCount == 0){
                isLocked = false;
                notify();
            }
        }
    }
}

????從代碼實現(xiàn)來看,可重入鎖增加了兩個狀態(tài),鎖的計數(shù)器和被鎖的線程,實現(xiàn)基本上和不可重入的實現(xiàn)一樣,如果不同的線程進來,這個鎖是沒有問題的,但是如果進行遞歸計算的時候,如果加鎖,不可重入鎖就會出現(xiàn)死鎖的問題。



4、ReentrantLock如何實現(xiàn)可重入的

使用ReentrantLock你要知道:
ReentrantLock支持公平非公平2種創(chuàng)建方式,默認(rèn)創(chuàng)建的是非公平模式的鎖。

看下它的構(gòu)造方法:

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

看下非公平鎖,它是繼承抽象類Sync的:

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() {
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

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

看下公平鎖,它也是繼承抽象類Sync的:

static final class FairSync extends Sync {
    private static final long serialVersionUID = -3000897897090466540L;

    final void lock() {
        acquire(1);
    }

    /**
     * Fair version of tryAcquire.  Don't grant access unless
     * recursive call or no waiters or is first.
     */
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        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;
    }
}

????NonfairSync、FairSync 和抽象類Sync 都是ReentrantLock的內(nèi)部類。

????Sync的定義,它是繼承AbstractQueuedSynchronizer的,AbstractQueuedSynchronizer既是我們常說的AQS(后面我也會整理一篇)

abstract static class Sync extends AbstractQueuedSynchronizer {
}

????好了,繼承關(guān)系清楚了 ,現(xiàn)在我們看下ReentrantLock是如何實現(xiàn)可重入的

????我們在addOne()和get()兩個方法加鎖的地方都打上斷點。然后開始調(diào)式:

  • addOne方法獲取鎖的時候走到NonfairSync的“compareAndSetState(0, 1)”,通過CAS設(shè)置state的值為1,調(diào)用成功,并設(shè)置當(dāng)前鎖被持有的線程為當(dāng)前線程t1;
  • 繼續(xù)調(diào)試,get方法獲取鎖的時候走到NonfairSync的“compareAndSetState(0, 1)”,通過CAS設(shè)置state的值為1,調(diào)用失敗(因為已經(jīng)被當(dāng)前線程t1鎖占有),走到else里面,繼續(xù)往里看;
  • 走到NonfairSync的tryAcquire方法,再往里走;
  • 會調(diào)用Sync抽象類里面的nonfairTryAcquire方法。源碼解釋我都寫在下面了。
final boolean nonfairTryAcquire(int acquires) {
    // 當(dāng)前線程
    final Thread current = Thread.currentThread();
// state變量的值
    int c = getState();
// 因為c當(dāng)前值為1,所以走else里面
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
// 判斷當(dāng)前線程 是不是 當(dāng)前鎖被持有的線程 ,判斷為 true
    else if (current == getExclusiveOwnerThread()) {
// c + acquires = 1 + 1 = 2
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);// 將state的值賦值為2
        return true;
    }
    return false;
}

????到此,可重入鎖加鎖的過程分析完畢。解鎖的過程一樣,希望你能自己debug下【調(diào)用的是Sync抽象類里面的tryRelease方法】

????我這里總結(jié)一下:

  • 當(dāng)線程嘗試獲取鎖時,可重入鎖先嘗試獲取并更新state值
    如果state == 0表示沒有其他線程在執(zhí)行同步代碼,則通過CAS把state置為1 會成功,當(dāng)前線程繼續(xù)執(zhí)行。
    如果status != 0,通過CAS把state置為1 會失敗,然后判斷當(dāng)前線程是否是獲取到這個鎖的線程,如果是的話執(zhí)行state+1,且當(dāng)前線程可以再次獲取鎖。

  • 釋放鎖時,可重入鎖同樣先獲取當(dāng)前state的值,在當(dāng)前線程是持有鎖的線程的前提下。
    如果status-1 == 0,則表示當(dāng)前線程所有重復(fù)獲取鎖的操作都已經(jīng)執(zhí)行完畢,然后該線程才會真正釋放鎖。

????你需要注意的是state變量的定義,其實AQS的實現(xiàn)類都是通過控制state的值來控制鎖的狀態(tài)的。它被volatile所修飾,能保證可見性。

private volatile int state;

????擴展:如果要通過AQS的state來實現(xiàn)非可重入鎖怎么實現(xiàn)呢?明確這兩點就可以了:

  • 獲取鎖時:去獲取并嘗試更新當(dāng)前status的值,如果status != 0的話會導(dǎo)致其獲取鎖失敗,當(dāng)前線程阻塞。
  • 釋放鎖時:在確定當(dāng)前線程是持有鎖的線程之后,直接將status置為0,將鎖釋放。



5、可重入鎖的特點

????可重入鎖的一個優(yōu)點是可一定程度避免死鎖。
????可重入鎖能避免一定線程的等待,可想而知可重入鎖性能會高于非可重入鎖。你可以寫程序測試一下哦!??!

推薦閱讀:
Java內(nèi)存模型-volatile的應(yīng)用(實例講解)
synchronized解決原子性-synchronized的三種應(yīng)用方式(實例講解)
線程池-一文弄懂Java里面的線程池ThreadPoolExecutor
可重入鎖-面試題:synchronized是可重入鎖嗎
大徹大悟synchronized原理,鎖的升級

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容