本文從單例模式的通常實現方式開始提及,逐步深刻到雙重加鎖實現。安全
1. 首先介紹一下最簡單的單例模式——餓漢模式,這種方式在單例類被加載的時候實例化。代碼實現以下:性能
1 public class Singleton { 2 private static Singleton instance; 3 4 static { 5 instance = new Singleton(); 6 } 7 8 private Singleton() { 9 } 10 11 public static Singleton getInstance() { 12 return instance; 13 } 14 }
餓漢模式的缺點在於,若是單例對象的建立過程比較耗時,那麼應用程序的啓動將會比較慢。spa
2. 爲了克服餓漢模式的缺點,將單例對象的建立過程延後到第一次使用單例對象時,這種實現方式被稱爲懶漢模式。代碼實現以下:線程
1 public class Singleton { 2 private static Singleton instance; 3 4 private Singleton() { 5 } 6 7 public static Singleton getInstance() { 8 if (instance == null) { 9 instance = new Singleton(); 10 } 11 12 return instance; 13 } 14 }
須要注意的是這種實現方式是線程不安全的。假設在單例類被實例化以前,有兩個線程同時在獲取單例對象,線程1在執行完第8行 if (instance == null) 後,線程調度機制將 CPU 資源分配給線程2,此時線程2在執行第8行 if (instance == null) 時也發現單例類尚未被實例化,這樣就會致使單例類被實例化兩次。爲了防止這種狀況發生,須要對 getInstance() 方法同步。下面看改進後的懶漢模式:code
1 public class Singleton { 2 private static Singleton instance; 3 4 private Singleton() { 5 } 6 7 // 線程安全的懶漢模式 8 public synchronized static Singleton getInstance() { 9 if (instance == null) { 10 instance = new Singleton(); 11 } 12 13 return instance; 14 } 15 }
3. 雙重加鎖(double check)對象
第2種實現方式中,每次獲取單例對象時都會加鎖,這樣就會帶來性能損失。雙重加鎖實現本質也是一種懶漢模式,相比第2種實現方式將會有較大的性能提高。代碼實現以下:blog
1 public class Singleton { 2 private volatile static Singleton instance; 3 4 private Singleton() { 5 } 6 7 public static Singleton getInstance() { 8 if (instance == null) { 9 synchronized (Singleton.class) { 10 if (instance == null) { 11 instance = new Singleton(); 12 } 13 } 14 } 15 16 return instance; 17 } 18 }
就算在單例類被實例化時有多個線程同時經過了第8行代碼 if (instance == null) 的判斷,但同一時間只有一個線程得到鎖後進入臨界區。經過第8行判斷的每一個線程會依次得到鎖進入臨界區,因此進入臨界區後還要再判斷一次單例類是否已被其它線程實例化,以免屢次實例化。因爲雙重加鎖實現僅在實例化單例類時須要加鎖,因此相較於第2種實現方式會帶來性能上的提高。另外須要注意的是雙重加鎖要對 instance 域加上 volatile 修飾符。因爲 synchronized 並非對 instance 實例進行加鎖(由於如今還並無實例),因此線程在執行完第11行修改 instance 的值後,應該將修改後的 instance 當即寫入主存(main memory),而不是暫時存在寄存器或者高速緩衝區(caches)中,以保證新的值對其它線程可見。資源
補充:第9行能夠鎖住任何一個對象,要進入臨界區必須得到這個對象的鎖。因爲並不知道其它對象的鎖的用途,因此這裏最好的方式是對 Singleton.class 進行加鎖。get