單例模式的雙重加鎖實現


本文從單例模式的一般實現方式開始說起,逐步深入到雙重加鎖實現。

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 }

餓漢模式的缺點在於,如果單例對象的創建過程比較耗時,那么應用程序的啟動將會比較慢。

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() 方法同步。下面看改進后的懶漢模式:

 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種實現方式將會有較大的性能提升。代碼實現如下:

 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 進行加鎖。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM