Synchronized

synchronized 關鍵字底層原理屬於 JVM 層面。原理如下:

① synchronized 同步語句塊的情況

public class SynchronizedDemo {	
    public void method() {	
	synchronized (this) {	
	System.out.println("synchronized 代碼塊");
		}
	}
}

編譯後生成字節碼文件通過執行javap -c -s -v -l SynchronizedDemo.class

synchronized關鍵字原理

從上面我們可以看出:

synchronized 同步語句塊的實現使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代碼塊的開始位置,monitorexit 指令則指明同步代碼塊的結束位置。 當執行 monitorenter 指令時,線程試圖獲取鎖也就是獲取 monitor(monitor對象存在於每個Java對象的對象頭中,synchronized 鎖便是通過這種方式獲取鎖的,也是爲什麼Java中任意對象可以作爲鎖的原因) 的持有權。當計數器爲0則可以成功獲取,獲取後將鎖計數器設爲1也就是加1。相應的在執行 monitorexit 指令後,將鎖計數器設爲0,表明鎖被釋放。如果獲取對象鎖失敗,那當前線程就要阻塞等待,直到鎖被另外一個線程釋放爲止。

② synchronized 修飾方法的的情況

public class SynchronizedDemo2 {
	public synchronized void method() {
	System.out.println("synchronized 方法");
	}
}

synchronized關鍵字原理

synchronized 修飾的方法並沒有 monitorenter 指令和 monitorexit 指令,取得代之的確實是 ACC_SYNCHRONIZED 標識,該標識指明瞭該方法是一個同步方法,JVM 通過該 ACC_SYNCHRONIZED 訪問標誌來辨別一個方法是否聲明爲同步方法,從而執行相應的同步調用。

使用方法:

synchronized關鍵字最主要的三種使用方式:

  • 修飾實例方法: 作用於當前對象實例加鎖,進入同步代碼前要獲得當前對象實例的鎖

  • 修飾靜態方法: :也就是給當前類加鎖,會作用於類的所有對象實例,因爲靜態成員不屬於任何一個實例對象,是類成員( static 表明這是該類的一個靜態資源,不管new了多少個對象,只有一份)。所以如果一個線程A調用一個實例對象的非靜態 synchronized 方法,而線程B需要調用這個實例對象所屬類的靜態 synchronized 方法,是允許的,不會發生互斥現象,因爲訪問靜態 synchronized 方法佔用的鎖是當前類的鎖,而訪問非靜態 synchronized 方法佔用的鎖是當前實例對象鎖。

  • 修飾代碼塊: 指定加鎖對象,對給定對象加鎖,進入同步代碼庫前要獲得給定對象的鎖。

總結: synchronized 關鍵字加到 static 靜態方法和 synchronized(class)代碼塊上都是是給 Class 類上鎖。synchronized 關鍵字加到實例方法上是給對象實例上鎖。儘量不要使用 synchronized(String a) 因爲JVM中,字符串常量池具有緩存功能!

使用:

public class SingletonDemo3 {
    /*
    雙重鎖鑑定
     */
    private SingletonDemo3() {
    }

    private volatile static SingletonDemo3 instance;

    public SingletonDemo3 getInstance() {
        if (instance == null) {
            synchronized (SingletonDemo3.class) {
                if (instance == null) { //volatile保證可見性,禁止指令重排序
                    instance = new SingletonDemo3();
                }
            }
        }
        return instance;
    }

    public static void main(String[] args) {
        SingletonDemo3 s = new SingletonDemo3();
        System.out.println(s.getInstance());
    }
}

instance 採用 volatile 關鍵字修飾也是很有必要的,

instance = new SingletonDemo3(); 這段代碼其實是分爲三步執行:
  1. 爲 instance 分配內存空間

  2. 初始化 instance

  3. 將 instance指向分配的內存地址

但是由於 JVM 具有指令重排的特性,執行順序有可能變成 1->3->2。指令重排在單線程環境下不會出先問題,但是在多線程環境下會導致一個線程獲得還沒有初始化的實例。例如,線程 T1 執行了 1 和 3,此時 T2 調用 getInstance() 後發現 instance不爲空,因此返回 instance,但此時 instance還未被初始化。

使用 volatile 可以禁止 JVM 的指令重排,保證在多線程環境下也能正常運行。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章