1. 程式人生 > 其它 >java 說清楚可重入鎖、不可重入鎖

java 說清楚可重入鎖、不可重入鎖

可重入鎖

廣義上的可重入鎖指的是可重複可遞迴呼叫的鎖,在外層使用鎖之後,在內層仍然可以使用,並且不發生死鎖(前提得是同一個物件或者class),這樣的鎖就叫做可重入鎖。

我的理解就是,某個執行緒已經獲得某個鎖,可以無需等待而再次獲取鎖,並且不會出現死鎖(不同執行緒當然不能多次獲得鎖,需要等待)。

簡單的說,就是某個執行緒獲得某個鎖,之後可以不用等待而再次獲取鎖且不會出現死鎖。

常見的可重入鎖

Synchronized和ReentrantLock 都是可重入鎖。

可重入鎖的釋放

同一個執行緒獲取同一個鎖,狀態值state會累加,假設state累加到了2,每釋放一次鎖會減1,只有當狀態值state減到0了,其他執行緒才有機會獲取鎖。也就是說,state歸零才是已釋放鎖的標緻。

可重入鎖示例

public class ReentrantTest implements Runnable {

    @Override
    public void run() {
        get();
    }

    public synchronized void get() {
        System.out.println(Thread.currentThread().getName());
        set();
    }

    /**
     * 遞迴方法
     */
    public synchronized void set() {
        System.out.println(Thread.currentThread().getName());
    }

    /**
     * 這裡的物件鎖只有一個,就是rt物件的鎖。
     * 當執行rt的get方法時,該執行緒獲得rt物件的鎖。在get方法內執行set方法時再次請求rt物件的鎖,因為synchronized是可重入鎖,所以又可以得到該鎖。迴圈這個過程。
     * 假設不是可重入鎖的話,那麼請求的過程中會出現阻塞,從而導致死鎖。
     * @param args
     */
    public static void main(String[] args) {
        ReentrantTest rt = new ReentrantTest();
        // for(;;)模擬無限迴圈
        for(;;){
            new Thread(rt).start();
        }
    }
}

分析:這裡的物件鎖只有一個,就是rt物件的鎖。當執行rt的get方法時,該執行緒獲得rt物件的鎖。在get方法內執行set方法時再次請求rt物件的鎖,因為synchronized是可重入鎖,所以又可以得到該鎖。迴圈這個過程。假設不是可重入鎖的話,那麼請求的過程中會出現阻塞,從而導致死鎖。

死鎖

多執行緒中,不同的執行緒都在等待其它執行緒釋放鎖,而其它執行緒由於一些原因遲遲沒有釋放鎖。程式的執行處於阻塞狀態,不能正常執行也不能正常終止。

執行結果

set()和get()同時輸出了相同的執行緒名稱,也就是說某個執行緒執行的時候,不僅進入了set同步方法,還進入了get同步方法。遞迴使用synchronized也沒有發生死鎖,證明其是可重入的。

可重入鎖的實現原理?

每一個鎖關聯一個執行緒持有者和計數器,當計數器為 0 時表示該鎖沒有被任何執行緒持有,那麼任何執行緒都可能獲得該鎖而呼叫相應的方法;當某一執行緒請求成功後,JVM會記下鎖的持有執行緒,並且將計數器置為 1;此時其它執行緒請求該鎖,則必須等待;而該持有鎖的執行緒如果再次請求這個鎖,就可以再次拿到這個鎖,同時計數器會遞增1;當執行緒退出同步程式碼塊時,計數器會遞減1,如果計數器為 0,則釋放該鎖。

再分析一下上面可重入鎖的例子

遞迴呼叫一次同步程式碼塊,計數器會變為2,整個遞迴呼叫執行完,先退出內層執行減1,再退出外層執行減1。然後釋放鎖。

不可重入鎖

就是某個執行緒已經獲得某個鎖,之後不可以再次獲取鎖,會被阻塞。

設計一個不可重入鎖

public class Lock {
    private boolean isLocked = false;
    /**
     * 加鎖
     */
    public synchronized void lock() throws Exception{
        while(isLocked){
            //當前執行緒釋放鎖,讓出CPU,進入等待狀態,直到被喚醒,才繼續執行15行
            wait();
            System.out.println("wait");
        }
        isLocked = true;
    }
    /**
     * 解鎖
     */
    public synchronized void unlock(){
        isLocked = false;
        //喚醒一個等待的執行緒繼續執行
        notify();
    }
}

測試

public class Test {
    Lock lock = new Lock();
    public void print() throws Exception{
        //加鎖 標記為true
        lock.lock();
        //釋放鎖->等待 阻塞在16行
        doAdd();
        lock.unlock();
    }
    public void doAdd() throws Exception{
        lock.lock();
        System.out.println("doAdd");
        lock.unlock();
    }

    public static void main(String[] args)throws Exception {
        Test test=new Test();
        test.print();
    }
}

結果:這裡,雖然模擬的是不可重入鎖,實際還是在單執行緒環境中的。當前執行緒執行print()方法首先加鎖 標記為true,接下來釋放鎖->等待 阻塞在16行內部的14行。整個過程中,第一次進入lock同步方法,執行完畢,第二次進入lock同步方法,阻塞等待。這個例子很好的說明了不可重入鎖。