1. 程式人生 > 其它 >synchronized 鎖是可重入鎖嗎?如何驗證?

synchronized 鎖是可重入鎖嗎?如何驗證?

摘要:舉例證明 synchronized鎖 是可重入鎖,並描述可重入鎖的實現原理。

綜述

  先給大家一個結論:synchronized鎖 是可重入鎖

  關於什麼是可重入鎖,通俗來說,當執行緒請求一個由其它執行緒持有的物件鎖時,該執行緒會阻塞,而當執行緒請求由自己持有的物件鎖時,如果該鎖是重入鎖,請求就會成功,否則阻塞。或者說,可重入鎖是同一個執行緒重複請求由自己持有的鎖物件時,可以請求成功而不會發生死鎖。與多執行緒併發執行的執行緒安全不同,可重入強調對單個執行緒執行時重新進入同一個子程式仍然是安全的。可重入鎖又稱遞迴鎖。

驗證可重入

  假設我們現在不知道它是不是一個可重入鎖,那我們就應該想方設法來驗證它是不是可重入鎖?怎麼驗證呢?看下面的程式碼!

public class SuperSalesman {

    public int ticketNum = 10;

    public synchronized void superSaleTickets()  {
        ticketNum --;
        System.out.println("父類售票後,剩餘票數:" + ticketNum
                + " " + Thread.currentThread().getName());
        try {
            Thread.sleep(30);
        } catch (InterruptedException e) {
            System.out.println("error, " + e);
        }
    }

}

  建立子類:

public class ChildSalesman extends SuperSalesman {

    public static void main(String[] args) {
        ChildSalesman child = new ChildSalesman();
        child.childSaleTickets();
    }

    public synchronized void childSaleTickets() {
        while (ticketNum > 0) {
            ticketNum --;
            System.out.println("子類售票後,餘票為:" + ticketNum
                    + " " + Thread.currentThread().getName());
            superSaleTickets(); //允許進入,synchronized的可重入性
        }
    }

    @Override
    public synchronized void superSaleTickets() {
        System.out.println("I am working");
        super.superSaleTickets();
    }
}

  現在執行一下上面的父子類繼承程式碼,我們看一下結果:

子類售票後,餘票為:9 main
I am working
父類售票後,剩餘票數:8 main
子類售票後,餘票為:7 main
I am working
父類售票後,剩餘票數:6 main
子類售票後,餘票為:5 main
I am working
父類售票後,剩餘票數:4 main
子類售票後,餘票為:3 main
I am working
父類售票後,剩餘票數:2 main
子類售票後,餘票為:1 main
I am working
父類售票後,剩餘票數:0 main

Process finished with exit code 0

  現在可以驗證出 synchronized 是可重入鎖了吧!因為這些方法輸出了相同的執行緒名稱,表明即使遞迴呼叫synchronized修飾的方法,也沒有發生死鎖,證明其是可重入的。

  下面是多個方法巢狀呼叫的例子:

public class SyncTest {

    public static void main(String[] args) {
        LockTest lock = new LockTest();
        lock.method1();
    }
}

public class LockTest {
    public synchronized void method1() {
        System.out.println("method1");
        method2();
    }

    public synchronized void method2() {
        System.out.println("method2");
        method3();
    }

    public synchronized void method3() {
        System.out.println("method3");
    }
}

  執行main方法,控制檯列印資訊如下,說明不會因為之前已經獲取過鎖還沒釋放而發生阻塞。即同一執行緒可執行多個持有同一把鎖的方法。

/Library/Java/JavaVirtualMachines/jdk-17.0.2.jdk ...
method1
method2
method3

  可以看到呼叫的三個方法均得到了執行。我們知道synchronized修飾普通方法時,使用的是物件鎖,也就是SuperSalesman物件。三個方法的鎖都是SuperSalesman物件。我們在子類中執行childSaleTickets方法時,獲取了SuperSalesman物件鎖,然後在childSomeString時呼叫了重寫父類的superSaleTickets方法,該方法的鎖也是SuperSalesman物件鎖,然後在其中呼叫父類的superSaleTickets方法,該方法的鎖也是SuperSalesman物件鎖。一個鎖多次請求,而且都成功了,所以synchronized是可重入鎖。

  所以在 java 內部,同一執行緒在呼叫自己類中其它 synchronized 方法/塊或呼叫父類的 synchronized 方法/塊都不會阻礙該執行緒的執行。就是說同一執行緒對同一個物件鎖是可重入的,而且同一個執行緒可以獲取同一把鎖多次,也就是可以多次重入。因為java執行緒是基於“每個執行緒(per-thread)”,而不是基於“每次呼叫(per-invocation)”的(java中執行緒獲得物件鎖的操作是以執行緒為粒度的,per-invocation 互斥體獲得物件鎖的操作是以每次呼叫作為粒度的)。

可重入鎖的實現原理

  看到這裡,你終於明白了 synchronized 是一個可重入鎖。但是面試官要再問你,可重入鎖的原理是什麼?

解釋一

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

解釋二

  通過javap -c SynchronizedLock.class 反編譯,來解析synchronized可重入鎖原理:synchronized通過monitor計數器實現,當執行monitorenter命令時:判斷當前monitor計數器值是否為0,如果為0,則說明當前執行緒可直接獲取當前鎖物件;否則,判斷當前執行緒是否和獲取鎖物件執行緒是同一個執行緒。若是同一個執行緒,則monitor計數器累加1,當前執行緒能再次獲取到鎖;若不是同一個執行緒,則只能等待其它執行緒釋放鎖資源。當執行完synchronized鎖物件的程式碼後,就會執行monitorexit命令,此時monitor計數器就減1,直至monitor計數器為0時,說明鎖被釋放了。

結束語

  如果您覺得本文對您有幫助,請點一下“推薦”按鈕,您的【推薦】將是我最大的寫作動力!歡迎各位轉載,但是未經作者本人同意,轉載文章之後必須在文章頁面明顯位置給出作者和原文連線;否則,樓蘭胡楊保留追究法律責任的權利。

Reference