初窺設計模式——單例模式

資料借鑑:http://cantellow.iteye.com/blog/838473java

簡單介紹:
  單例模式是一種常常用到的軟件設計模式。在它的核心結構中只包含一個被稱爲單例的特殊類。經過單例模式能夠保證系統中應用該模式的類只有一個實例。即一個類只有一個實例
定義:
  一個類有且只有一個實例,而且自行實例化向整個系統提供。
特色:
       一、單例類只能有一個實例。
  二、單例類必須本身建立本身的惟一實例。
  三、單例類必須給全部其餘對象提供這一實例。
實現方式:
  通常來講,咱們有如下幾個必要的操做:
    一、私有化構造方法;
    二、final類(定義爲不可繼承,這點書上沒有提到,暫時還在研究)
    三、將類的實例對象定義爲一個私有的屬性(不限制爲成員變量)
    四、經過getInstance()方法獲取實例,若私有的實例屬性對象引用不爲空則返回,不然實例化該屬性並返回
  這裏先介紹四種實現方式
      一、餓漢模式
    二、懶漢模式
    三、雙重驗證
    四、靜態內部類模式
 
先看第一種方式,餓漢模式顧名思義,火燒眉毛的想要吃(初始化實例),在類中定義一個私有靜態本類的實例化對象,在類加載的過程就進行此對象的實例化,以後的對此類實例的調用都是調用此實例。
代碼以下:
public class Singleton2 {

    private static Singleton2 singleton2= new Singleton2();

    private Singleton2(){}

    public static Singleton2 getInstance() {
        return singleton2;
    }

    public static String getStr() {
        return "Create String type Object";
    }
}
餓漢模式是較爲簡單的實現方式,一樣也是較爲經常使用的方式。但他有着必定的缺陷:雖然在單例模式中大多都只調用getInstance()方法,但不排除有其餘的方式致使類加載,好比若是類中getStr()這種與類的實例無關的方法若是被調用,就會觸發類加載,從而對靜態成員進行初始化,可是此類有可能並不須要實例化,這樣在某種程度上會形成必定的資源浪費。也就沒法達到lazy loading的效果。
 
這就引入了懶漢模式
 
懶漢模式:只有在第一此調用類的實例對象時纔會初始化類的實例,從而實現延遲加載
代碼以下
public class Singleton3 {

    private static Singleton3 singleton2 = null;

    private Singleton3(){
        Tools.println("類實例化");
    }

    public static synchronized Singleton3 getInstance(){
        if(singleton2 == null)
            singleton2 = new Singleton3();
        return singleton2;
    }

    public static void CreateString(){
        Tools.print("Create String in Singleton3");
    }
}
懶漢模式經過getInstance()方法建立實例,這樣只有在使用到實例的時候纔會初始化實例對象,實現了延遲加載,可是這種模式會出現線程同步問題:一個線程調用了getInstace()方法,判斷爲空後進行實例的建立,此時又有了一個線程調用了getInstance()方法,但此刻第一個線程尚未完成實例化的操做,故此線程也會實例化一個對象。因此咱們須要爲getInstance()方法加上同步關鍵字synchronized 。
那麼問題來了,咱們使用延遲加載就是爲了提高系統性能,而引入了同步關鍵字則會大大影響多線程狀況下的性能,因此此方式也有這很大的缺陷。
 
下面就引入了雙重檢測方式
 
雙重檢測方式:經過雙重檢測的方式完成延遲加載
代碼以下:
class Singleton1 {

    private Singleton1() {
    }

    public static Singleton1 instance = null;

    public static Singleton1 getInstance() {
        if (instance == null) {
            synchronized (Singleton1.class) {
                if (instance == null) {
                    instance = new Singleton1();
                }
            }
        }
        return instance;
    }
}
能夠看到,首先判斷實例對象是否爲空,若是判斷經過再進行同步操做。
這種方式是解決了懶漢模式的效率問題,但同時也有一些問題,第一次加載時反應不快,因爲java內存模型一些緣由偶爾失敗。失敗緣由能夠詳見 http://blog.csdn.net/chenchaofuck1/article/details/51702129
 
接下來引入一種已經較爲完善而且使用較多的一種實現方式:靜態內部類實現
代碼以下:
public class Singleton4 {
private Singleton4(){}
static class SingletonHolder {
private static Singleton4 singleton = new Singleton4();
}
public static Singleton4 getInstance(){
return SingletonHolder.singleton;
}
}
此方式利用了:靜態內部類並不會在外部類類加載的時候也進行類加載,而是在它自身第一次被使用時進行類加載,而且jvm的類加載過程是對線程很是友好的,因此咱們不須要擔憂同步問題。
public class Singleton4 {

    private Singleton4(){}

    static class SingletonHolder {
        private static Singleton4 singleton = new Singleton4();
    }

    public static Singleton4 getInstance(){
        return SingletonHolder.singleton;
    }
}
 
上述方法都是基本上實現了單例模式,可是依然有兩個問題須要注意:
1:若是單例由不一樣的類裝載器注入,那邊有可能存在有多個單例類的實例。假如不是遠端存取,假如一些servlet容器對每一個servlet使用不一樣的類裝載器,他們就會有個字的單例類的實例。
2:若是單例類實現了java.io.Serializable接口,那麼此類的實例就能夠被序列化和復原,若是序列化一個對象,而後復原多個此對象,就會出現多個單例類的實例。
關於此問題能夠參照此文章: http://blog.csdn.net/fg2006/article/details/6409423
 
第一個問題的解決方法:
private static Class getClass(String classname)      
                                         throws ClassNotFoundException {     
      ClassLoader classLoader = Thread.currentThread().getContextClassLoader();     
      
      if(classLoader == null)     
         classLoader = Singleton.class.getClassLoader();     
      
      return (classLoader.loadClass(classname));     
   }     
}
第二個問題的解決方法:
public class Singleton implements java.io.Serializable {     
   public static Singleton INSTANCE = new Singleton();     
      
   protected Singleton() {     
        
   }     
   private Object readResolve() {     
            return INSTANCE;     
      }    
}  
 
這兩種方法是我從其餘的博客上看來的,如今還在瞭解中。。。
 
 
應用場景:
1. Windows的Task Manager(任務管理器)就是很典型的單例模式(這個很熟悉吧),想一想看,是否是呢,你能打開兩個windows task manager嗎? 不信你本身試試看哦~ 
2. windows的Recycle Bin(回收站)也是典型的單例應用。在整個系統運行過程當中,回收站一直維護着僅有的一個實例。
3. 網站的計數器,通常也是採用單例模式實現,不然難以同步。
4. 應用程序的日誌應用,通常都何用單例模式實現,這通常是因爲共享的日誌文件一直處於打開狀態,由於只能有一個實例去操做,不然內容很差追加。
5. Web應用的配置對象的讀取,通常也應用單例模式,這個是因爲配置文件是共享的資源。
6. 數據庫鏈接池的設計通常也是採用單例模式,由於數據庫鏈接是一種數據庫資源。數據庫軟件系統中使用數據庫鏈接池,主要是節省打開或者關閉數據庫鏈接所引發的效率損耗,這種效率上的損耗仍是很是昂貴的,由於何用單例模式來維護,就能夠大大下降這種損耗。
7. 多線程的線程池的設計通常也是採用單例模式,這是因爲線程池要方便對池中的線程進行控制。
8. 操做系統的文件系統,也是大的單例模式實現的具體例子,一個操做系統只能有一個文件系統。
9. HttpApplication 也是單位例的典型應用。熟悉ASP.Net(IIS)的整個請求生命週期的人應該知道HttpApplication也是單例模式,全部的HttpModule都共享一個HttpApplication實例.
 
總結以上,不難看出:
  單例模式應用的場景通常發如今如下條件下:
  (1)資源共享的狀況下,避免因爲資源操做時致使的性能或損耗等。如上述中的日誌文件,應用配置。
  (2)控制資源的狀況下,方便資源之間的互相通訊。如線程池等。
相關文章
相關標籤/搜索