關於synchronized關鍵字的思考

  最近看了一篇大佬的博文,深有體會,來了一手依瓢畫葫蘆仿寫一篇關於synchronized關鍵字思考的博文。

  在Java中,synchronized關鍵字是用來控制線程同步的,就是在多線程的環境下,控制synchronized代碼段不被多個線程同時執行。synchronized既可以加在一段代碼上,也可以加在方法上。

  然而,給方法或代碼塊上加上synchronized關鍵字就萬事大吉了嗎?不妨運行一下下面這段代碼試試:

測試代碼


import java.util.concurrent.TimeUnit;

// synchronized鎖住的究竟是什麼?
public class ThreadTest3 implements Runnable {
    private final  Object Mutex=new Object();
      //方法1 static 共享對象


    @Override
    public void  run(){
        //方法2 synchronized (ThreadTest3.class)

        /*
        synchronized(Sync.class)實現了全局鎖的效果。

        static synchronized方法,static方法可以直接類名加方法名調用,方法中無法使用this,所以它鎖的不是this,而是類的Class對象,
        所以,static synchronized方法也相當於全局鎖,相當於鎖住了代碼段。
         */
      synchronized (Mutex){
            System.out.println("開始進行中----");
            try {
            //模擬程序進行過程
                TimeUnit.MILLISECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("結束");
        }
    }

    public static void main(String[] args){
      //  方法3 ThreadTest3 t=new ThreadTest3();
        for(int i=0;i<3;i++){
            ThreadTest3 t=new ThreadTest3();
            Thread newThread=new Thread(t);
            newThread.start();

        }
    }

  可見結果如下:

開始進行中—-
開始進行中—-
開始進行中—-
結束
結束
結束

   程序並沒有如我們想象中的那麼運行,原因是什麼呢,這要看synchronized究竟是對什麼起作用了,是代碼?還是對象? 相信當你把測試代碼瀏覽一遍的時候你心中已經有了答案。

  接下來,驗證你的猜測吧,在測試代碼註釋中有三種方法我們來測試一下:

  1. 將Mutex對象定義爲靜態變量,實現共享資源的唯一性。
  2. 將運用了synchronized關鍵字的方法改寫,改爲鎖住ThreadTest3.class這個對象
  3. 在測試運行的時候將循環下的第一句ThreadTest3的構造方法移到外面,從而只創建一個實例對象。

  接下來結果驗證如下:

開始進行中—-
結束
開始進行中—-
結束
開始進行中—-
結束

  可見,synchronized(this)以及非static的synchronized方法,只能防止多個線程同時執行同一個對象的同步代碼段,它鎖住的正是括號中的對象。

  在測試代碼中我設置了一個私有變量Mutex以便理解,其實可以將它註釋掉,在源代碼中對應位置改爲synchronized(this)是一個道理。

  而我們在代碼中所做的以上幾種修改其實也只有一個目的,那便是使它們鎖住的對象一致。

  在這裏再多談一句關於“鎖”的理解,這種說法其實並不是很嚴謹,準確的來說應該是某線程獲取了與Mutex(對象)關聯的monitor鎖,爲了方便起見,我們便直接以鎖來稱呼。

Q:爲甚叫monitor鎖?
A:synchronized關鍵字包括monitor enter和monitor exit兩個JVM指令,它能夠保證在任何時候任何線程執行到monitor enter成功之前都必須從主內存中獲取數據,而不是從緩存中,在monitor exit運行成功之後,共享變量被更新後的值必須刷入主內存。

   當synchronized鎖住一個對象後,別的線程如果也想拿到這個對象的monitor鎖 ,就必須等待這個線程執行完成釋放鎖,才能再次給對象加鎖,不然只能進入阻塞狀態。爲了達到線程同步的目的。即使兩個不同的代碼段,都要鎖住同一個引用(對象),這樣的話兩個代碼段便不能再多線程的狀態下併發執行。

  由於synchronized關鍵字具有排他性,即所有線程必須串行地經過synchronized保護的共享區域,synchronized作用域越大,其效率也會越低,所以我們在進行開發的時候應該注意儘量控制synchronized的作用域。

PS: 一個小總結

  • 對於非static用synchronized修飾的同步方法=方法內用synchronized(this)覆蓋代碼塊

      以下兩條方法是等價的:

public synchronized void method(){
   //執行代碼塊----
 }
public void method(){
  synchronized(this){
    //執行代碼塊
 }
}
  • 對於static用synchronized修飾的靜態方法=靜態方法內用synchronized(類名.class)覆蓋代碼塊

  以下兩條方法是等價的:

public static synchronized void method(){
  //執行代碼塊
}
public static void method(){
  synchronized(類名.class){
  //執行代碼塊
}
}

參考資料

1.https://www.cnblogs.com/QQParadise/articles/5059824.html
2.汪文君.Java高併發編詳解,2018(1):63-76.

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