1. 程式人生 > >java單例設計模式詳解

java單例設計模式詳解

在設計模式中,最常談及的就是單例設計模式。

百度百科對於單例設計模式的設計動機是這麼闡述的:

上述是一個廣義的概念,那麼在具體開發中單例帶來了什麼呢?

在java語言中,單例帶來了兩大好處:

1.對於頻繁使用的物件,可以省略建立物件所花費的時間,這對於那些重量級的物件而言,是非常可觀的一筆系統開銷。

2.由於new操作的次數減少,因而對系統記憶體的使用頻率也會降低,這將減輕GC壓力,縮短GC停頓時間。

所以對於系統的關鍵元件和被頻繁操作的物件,使用單例模式便可以有效地改善系統性能。

單例的參與者非常簡單,只有單例類和使用者兩個;

下面介紹單例設計模式在java程式碼的具體實現:

單例模式的核心在於通過一個介面返回唯一的物件例項,一個簡單的單例實現如下:

複製程式碼
 1 public class Singleton {
 2     private  Singleton() {
 3         System.out.println("Singleton  is  create"); // 建立單例的過程可能會比較慢
 4     }
 5 
 6     private static Singleton instance = new Singleton();
 7 
 8     private static Singleton getInstance() {
9 return instance; 10 } 11 }
複製程式碼

注意程式碼的藍色部分,首先單例類必須要有一個private訪問級別的建構函式,只有這樣,才能確保單例不會在系統中的其他程式碼內被例項化,這點是相當重要的;其次,instance成員和getInstance()方法必須是static的。

這種單例的實現方式非常簡單,而且十分可靠,它唯一的不足僅是無法對instance例項做延遲載入,假如單例的建立過程很慢,而由於instance成員變數是static定義的,因此在JVM載入單例類時,單例物件就會被建立,如果此時,這個單例類在系統中還扮演其他角色,那麼在任何使用這個單例類的地方都會初始化這個單例變數,根本就不會管是否被用到。比如單例String工廠,用於建立一些字串(該類既用於建立單例Singleton,又用於建立String物件)。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public  class  Singleton {      private  Singleton() {          System.out.println( "Singleton  is  create" );  // 建立單例的過程可能會比較慢      }        private  static  Singleton instance =  new  Singleton();        private  static  Singleton getInstance() {          return  instance;      }            public  static  void  CreateString(){        //這是模擬單例類扮演其他角色          System.out.println( "createString in Singleton" );      } }

  上面程式碼的意思就是,假如單例類裡面有getInstance以外的其它靜態方法,跟單例沒啥關係的方法,如果使用了Singleton.CreateString()這種呼叫,就會自動建立Singleton這個類例項(雖然private構造已經防止了你人為去new這個單例),這是開發人員不願看到的,我執行下面程式碼(此程式碼呼叫了上面程式碼的createString()方法)進行展示:

 

 那麼在這個單例基礎上進行延遲載入改良:

複製程式碼
 1 public class Singleton {
 2     private Singleton() {
 3         System.out.println("LazySingleton  is  create"); // 建立單例的過程可能會比較慢
 4     }
 5 
 6     private static Singleton instance = null;
 7 
 8     public static synchronized Singleton getInstance() {
 9         if (instance == null)
10             instance = new Singleton();
11         return instance;
12     }
13 
14 }
複製程式碼

此時再呼叫同樣的測試方法:

解決了這個問題。

 

顯而易見,這次改良主要是從JVM載入類的原理上進行改良的,上面的程式碼在初始化類的時候給instance賦予null,確保系統啟動的時候沒有額外的負載,其次,在getInstance方法中加入判斷單例是否存在,不存在才new。但是getInstance()方法必須是同步的,

否則多執行緒環境下,可能執行緒1正在建立單例時,執行緒2判斷單例instance為空,就會建立多個例項。

 

但是上面的寫法,存在效能問題,在多執行緒下,它的耗時遠遠大於第一種單例。以下程式碼就說明了此問題:

複製程式碼
1     @Override
2         public void run(){
3             for (int i = 0; i < 100000; i++) 
4                 Singleton.getInstance();
5                 System.out.println("spend:" (System.currentTimeMillis()-begintime));
6             
7         }
複製程式碼

開啟五個執行緒同時完成以上程式碼的執行,使用第一種單例耗時0ms,而使用第二種單例耗時390ms,效能至少相差兩個數量級。

為了執行緒安全使用了同步關鍵字反而降低了系統性能,為了解決這個問題,繼續改進:

複製程式碼
 1 public class Singleton {
 2     private Singleton() {
 3         System.out.println("LazySingleton  is  create"); // 建立單例的過程可能會比較慢
 4     }
 5 
 6     public static class SingletonHolder {
 7         private static Singleton instance = new Singleton();
 8     }
 9 
10     public static synchronized Singleton getInstance() {
11         return SingletonHolder.instance;
12     }
13 
14 }
複製程式碼

沒錯,這就是現在最完整的單例寫法,內部類實現單例,除了effctive java中闡述的列舉單例實現,這種方法是目前最好的實現方式。

在這個實現中,用內部類來保護單例,當Singleton類被載入時,內部類不會被初始化,所以可以確保Singleton類被載入JVM時,不會初始化單例類,當getInstance方法被呼叫時,才會載入SingleHolder,從而初始化instance,同時,由於例項的建立是在類載入時完成的,故天生對多執行緒友好,getInstance()方法也不需要使用synchronized修飾,因此,這種實現能兼顧前兩種寫法的優點(延遲載入,非同步)。

 最後,在極端情況下,序列化和反序列化可能會破壞單例,一般來說不多見,如果存在就要多加註意,此時可以加入以下程式碼:

1 2 3 private  Object readResolve() {          return  instance;      }