設計模式(一)-單例模式

一.單例模式作爲23種設計模式之一,也屬於三大類-創建型設計模式之一,其特點是對該類實例化只有一個對象

在Java應用中,單例對象能保證在一個JVM中,該對象只有一個實例存在。這樣的模式有幾個好處:
1、某些類創建比較頻繁,對於一些大型的對象,這是一筆很大的系統開銷。
2、省去了new操作符,降低了系統內存的使用頻率,減輕GC壓力。
3、有些類如交易所的核心交易引擎,控制着交易流程,如果該類可以創建多個的話,系統完全亂了。

單例模式的構建模式大體分爲兩類:餓漢式,懶漢式;

1.餓漢式

   class Singlton{

             //私有的構造方法

             private Singtlon(){}

             private static Singlton singlton = new Singlton();

            //提供一個公有方法給外部調用

            public static Singlton getSinglton(){

                 return singlton;    

             }

       }

2.懶漢式

  class Singlton{

             //私有的構造方法

             private Singtlon(){}

             private static Singlton singlton =null;

            //提供一個公有方法給外部調用

            public static Singlton getSinglton(){

                if(singlton==null){

                  singlton = new Singlton();

                }

                 return singlton;    

             }

       }


一些細節:

1.餓漢式比懶漢式線程安全;

2.關於懶漢式的線程安全的解決方案:

使用線程鎖的方式,對於單例模式來說,臨界資源是對象singtlon;

所以我們只要給臨界資源上鎖就可以啦!

  public static Singlton getSinglton(){

          if(singlton==null){

               synchronzied(singlton){

                  singlton = new Singlton();

               }

           }

                 return singlton;    

 }

3.還可以使用內部類來進行優化,JVM內部的機制能夠保證當一個類被加載的時候,這個類的加載過程是線程互斥的(就是加載完畢後別的線程才能使用)。
這樣當我們第一次調用getInstance的時候,JVM能夠幫我們保證instance只被創建一次,
並且會保證把賦值給instance的內存初始化完畢,這樣我們就不用擔心上面的問題。
同時該方法也只會在第一次調用的時候使用互斥機制,這樣就解決了低性能問題。

public class Singleton {  
 
/* 私有構造方法,防止被實例化 */  
private Singleton() {  
}  


/* 此處使用一個內部類來維護單例 */  
private static class SingletonFactory {  
   private static Singleton instance = new Singleton();  
}  


/* 獲取實例 */  
public static Singleton getInstance() {  
   return SingletonFactory.instance;  
}  


/* 如果該對象被用於序列化,可以保證對象在序列化前後保持一致 */  
private Object readResolve() {  
   return getInstance();  
}  
}

4.還有一個反射的問題

反射中有一個方法setAccessiable(true)(暴力反射),

在私有構造方法中進行處理

private static boolean flag;

/* 私有構造方法,防止被實例化 */  
private Singleton() {  
if(!flag){
flag = false;
}else{
throw new RuntimeException("不能多次創建單例對象");
}
}  

反射的問題處理完了之後,這裏還有一個問題,就是如果把單例對象進行序列化然後再反序列化,
那麼內存中就會出現倆個一樣的單例對象,只是內存地址不同。這種情況我們可以使用readResolve方法來防止。
private Object readResolve(){.....}
ObjectInputStream 會檢查對象的class是否定義了readResolve方法。
如果定義了,將由readResolve方法指定返回的對象。返回對象的類型一定要是兼容的,否則會拋出ClassCastException 。 
例子:
public abstract class Singleton8 implements Serializable{  


private static final long serialVersionUID = 7863921642928237696L;


/* 此處使用一個內部類來維護單例 */  
private static class SingletonFactory {  
   @SuppressWarnings("serial")
private static Singleton8 instance = new Singleton8(){};
}  

//測試方式,把單例對象序列化後再反序列化從而獲得一個新的對象 就相當於複製了一個單例對象
public Singleton8 copy() throws Exception{  
       ByteArrayOutputStream os = new ByteArrayOutputStream();  
       ObjectOutputStream oos = new ObjectOutputStream(os);  
       oos.writeObject(Singleton8.getInstance());  
         
       InputStream is = new ByteArrayInputStream(os.toByteArray());  
       ObjectInputStream ois = new ObjectInputStream(is);  
       Singleton8 obj = (Singleton8) ois.readObject();  
       return obj;  


/* 獲取實例 */  
public static Singleton8 getInstance() {  
   return SingletonFactory.instance;  
}  


/* 如果該對象被用於序列化,可以保證對象在序列化前後保持一致 */  
/* 把這個方法註釋前和註釋後來運行測試代碼觀察結果 */  
private Object readResolve() {  
   return getInstance();  
}  
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章