1. 程式人生 > 其它 >Java併發程式設計 —— synchronized關鍵字

Java併發程式設計 —— synchronized關鍵字

一、是什麼?(作用)

synchronized關鍵字解決了多個執行緒之間訪問資源的同步性問題,保證了被其修飾的方法或是程式碼塊在任意時刻只能有一個執行緒執行。

而在早期的Java版本中,synchronized屬於重量級鎖,效率低下。為什麼呢?

因為監視器鎖(monitor)是依賴底層的作業系統Mutex Lock來實現的,Java的執行緒是對映到作業系統的原生執行緒之上的。如果要掛起或喚醒一個執行緒,都需要作業系統幫忙完成,而作業系統實現執行緒之間的切換時需要從使用者態轉換到核心態,這個狀態之間的轉換需要相對比較長的時間,時間成本相對較高。
慶幸的是,在Java 6之後Java官方從JVM層面對synchronized

做了較大的優化,所以現在的synchronized鎖效率也優化的很不錯了。JDK 1.6對鎖的實現引入了大量的優化,如自旋鎖、適應性自旋鎖、鎖消除、鎖粗話、偏向鎖、輕量級鎖等技術來減少操作的開銷。
故而,目前不論是各種開源框架還是JDk原始碼都大量使用了synchronized關鍵字。


二、怎麼用?(實踐)

1、主要有三種使用方式:

  • 修飾例項方法:給當前物件例項加鎖。進入同步程式碼前要獲得 當前物件例項的鎖
synchronized void method(){
  //業務程式碼
}
  • 修飾靜態方法:給當前類加鎖。會作用於當前類的所有物件例項,進入同步程式碼前要獲得當前類的鎖。因為靜態成員不屬於任何一個例項物件,是類成員(static表明這是該類的一個靜態資源,無論new了多少物件,只有一份
    )。所以,如果一個執行緒A呼叫一個例項物件的非靜態synchronized方法,而執行緒B需要呼叫這個例項物件所屬類的靜態synchronized方法,是允許的,不會發生互斥現象的,因為訪問靜態synchronized方法佔用的鎖是當前類的鎖,而訪問非靜態synchronized方法佔用的鎖是當前例項物件的鎖,故而不矛盾。
synchronized static void method(){
  //業務程式碼
}
  • 修飾程式碼塊:給指定物件加鎖。synchronized(thisobject)表示進入同步程式碼塊前要獲得thisobject物件的鎖synchronized(類.class)
    表示進入同步程式碼前要獲得指定類的class鎖
synchronized(this){
  //業務程式碼
}

2、小結

  • synchronized關鍵字加在static靜態方法和synchronized(class)程式碼塊上都是給加鎖。
  • synchronized關鍵字加在例項方法上是給物件例項加鎖。
  • 儘量不要使用synchronized(String str),因為在JVM中,字串常量池具有快取功能!

三、為什麼?(底層原理)

synchronized關鍵字底層原理屬於JVM層面。可看 深入理解synchronized底層原理,一篇文章就夠了! 這篇文章。


四、面試問題

1、使用雙重校驗鎖實現物件單例(執行緒安全)

public class Singleton {
  private volatile static Singleton uniqueInstance;

  private Singleton() {}

  public static Singleton getUniqueInstance(){
    //先判斷物件是否已例項化過,若沒有方可進入加鎖程式碼塊
    if(uniqueInstance == null){
      //類物件加鎖
      synchronized(Singleton.class){
        if(uniqueInstance == null){
          uniqueInstance = new Singleton();
        }
      }
    }
    return uniqueInstance;
  }
}

需要注意的是uniqueInstance採用volatile關鍵字修飾是很必要的。為什麼呢?uniqueInstance = new Singleton();這段程式碼其實是分三步執行的:

  • 1、為uniqueInstance分配記憶體空間
  • 2、初始化uniqueInstance
  • 3、將uniqueInstance指向分配的記憶體地址

但由於JVM具有指令重排序的特性,執行順序有可能變成 1 -> 3 -> 2。指令重排序在單執行緒環境下不會出現問題,但是在多執行緒環境下會導致一個執行緒獲得還沒有初始化的例項。例如,執行緒T1執行了1和3,此時T2呼叫getUniqueInstance()後發現uniqueInstance不為空,因此返回uniqueInstance,但此時uniqueInstance還未被初始化。
此處就不做細分析,簡而言之,使用volatile可以禁止JVM的指令重排,保證上述程式碼在多執行緒環境下也能正常執行。

2、構造方法能否使用synchronized關鍵字修飾?

回答:不可以,構造方法本身就是執行緒安全的,不存在同步的構造方法一說。