Java設計模式之單例模式,這是最全最詳細的了

尚學堂明輝發表於2019-01-19

一、單例模式

作用:保證一個類只有一個例項,並且提供一個訪問該例項的全域性訪問入口

1、單例模式的常用
1.Windows的工作管理員
2.Windows的回收站,也是一個單例應用
3.專案中的讀取配置檔案的物件
4.資料庫的連線池
5.Servlet中的Application Servlet
6.Spring中的Bean預設也是單例的
7.SpringMVC Struts中的控制器

2、單例模式的優點
1.由於單例模式只生成一個例項,減少了系統給的效能開銷,當一個物件需要產生時,當時消耗的資源較多。那麼產生物件時構建的方式就可以通過單例去構建。
2.單例模式存在全域性訪問點,所以可以優化共享資源訪問。

3、常見的單例模式的構建方法
1.餓漢式:執行緒安全 呼叫率高 但是不能延遲載入
2.懶漢式:執行緒安全 呼叫率不高 但是可以延遲載入
3.雙重檢測(double check )
4.靜態內部類(執行緒安全 可以延遲載入)
5.列舉單例 執行緒安全 不可以延遲載入

二、程式碼案例展示

1、餓漢式

/** 
* 餓漢式: 
*      類只要被載入就會被載入全域性變數,所以餓漢式,會被及時載入。(沒有懶載入 ) 
*      並且存在天然的執行緒安全問題。 
* @author 碼歌老薛 
* @date 建立時間 猴年馬月 
* @version 1.0 
*/  
public class SingleHungry {  
 //提供靜態的全域性變數 作為訪問該類例項的入口  
 private static SingleHungry sh = new SingleHungry();  
 /** 
* 構造器私有 無法建立物件 
 */  
 private SingleHungry(){  
       
}  
 /** 
* 對外提供get方法獲取 該類的例項 
* @return 
 */  
public static SingleHungry getInstance(){  
return sh;  
 }  
}  
  

2、懶漢式

/** 
 * 懶漢式: 
*      全域性變數初始化放到了例項化方法中,延遲產生物件。 
 *      但是當多個執行緒統一訪問時,有可能出現執行緒不安全的情況。需要優化。 
 * @author 碼歌老薛 
 * @date 建立時間 猴年馬月 
 * @version 1.0 
*/  
public class SingleLazy implements Serializable{  
   //提供靜態的全域性變數 作為訪問該類例項的入口 但是這裡不立即載入  
    private static SingleLazy sh = null;  
      
     
    /** 
     * 構造器私有 無法建立物件 
     */  
    private SingleLazy(){   
       System.out.println("建構函式被呼叫了");  
   }  
      
   /** 
     * 對外提供get方法獲取 該類的例項 
    * @return 
    * @throws InterruptedException  
     */  
    public static synchronized SingleLazy getInstance() {  
       if(sh==null){  
            sh = new SingleLazy();  
       }  
        return sh;  
         
    }  
        
} 

上海尚學堂java培訓 shsxt.com

3、雙重檢測

/** 
 * 懶漢式: 
 *      全域性變數初始化放到了例項化方法中,延遲產生物件。 
 *      但是當多個執行緒統一訪問時,有可能出現執行緒不安全的情況。需要優化。 
 * @author 碼歌老薛 
 * @date 建立時間 猴年馬月 
 * @version 1.0 
 */  
public class SingleLazy4 {  
    //提供靜態的全域性變數 作為訪問該類例項的入口 但是這裡不立即載入  
    private volatile  static SingleLazy4 sh = null;  
      
      
    /** 
     * 構造器私有 無法建立物件 
     */  
    private SingleLazy4(){  
        System.out.println("被呼叫了");  
    }  
      
    /** 
     * 雙重校驗鎖式(也有人把雙重校驗鎖式和懶漢式歸為一類)分別在程式碼鎖前後進行判空校驗 
     * ,雙重校驗鎖式是執行緒安全的。然而,在JDK1.5以前,DCL是不穩定的,有時也可能建立多個例項, 
     * 在1.5以後開始提供volatile關鍵字修飾變數來達到穩定效果。 
     * 雙重校驗鎖DCL(double checked locking) 
     * @return 
     * @throws InterruptedException  
     */  
    public static SingleLazy4 getInstance() {  
        if(sh == null){  
            synchronized(SingleLazy4.class){  
                if(sh == null){  
                    sh = new SingleLazy4();  
                   //return singleton;    //有人提議在此處進行一次返回  
                }  
                //return singleton;    //也有人提議在此處進行一次返回  
            }  
        }  
        return sh;  
    }  
}  

上海尚學堂Java培訓 shsxt.com 獲取更多java學習資料

4、靜態內部類

/** 
 *靜態內部類 
 *
 * @author 碼歌老薛
 * @date 建立時間 猴年馬月 
 * @version 1.0 
 */  
public class SingleInner {  
      
    /** 
     *靜態內部類式和餓漢式一樣,同樣利用了ClassLoader的機制保證了執行緒安全; 
     *不同的是,餓漢式在Singleton類被載入時(從程式碼段3-2的Class.forName可見) 
     *就建立了一個例項物件,而靜態內部類即使Singleton類被載入也不會建立單例物件, 
     *除非呼叫裡面的getInstance()方法。因為當Singleton類被載入時 
     *,其靜態內部類SingletonHolder沒有被主動使用。只有當呼叫getInstance方法時, 
     *才會裝載SingletonHolder類,從而例項化單例物件。 
 
    這樣,通過靜態內部類的方法就實現了lazy loading,很好地將懶漢式和餓漢式結合起來, 
    既實現延遲載入,保證系統效能,也能保證執行緒安全  
     */  
    private static class SingleInnerHolder{  
        private static SingleInner instance = new SingleInner();  
    }  
      
    private SingleInner(){  
        System.out.println("我被呼叫了");  
   }  
    public static SingleInner getInstance(){  
        return SingleInnerHolder.instance;  
    }  
}  

5、列舉單例

/** 
 * jvm提供底層保證  
 * 不可能出現序列化、反射產生物件的漏洞 但是不能做到延遲載入 
在外部,可以通過EnumSingleton.INSTANCE.work()來呼叫work方法。預設的列舉例項的建立是執行緒安全的 
、,但是例項內的各種方法則需要程式設計師來保證執行緒安全。 
總的來說,使用列舉單例模式,有三個好處: 
 1.例項的建立執行緒安全,確保單例。2.防止被反射建立多個例項。3.沒有序列化的問題。 
 * @author 碼歌老薛 
 * @date 建立時間 猴年馬月 
 * @version 1.0 
 */  
public enum SingleEnum {  
    //例項化物件  
    INSTANCE;  
      
    /** 
     * 物件需要執行的功能 
     */  
    void getInstance(){  
          
    }  
}  

上海尚學堂java培訓 shsxt.com

6、反射/序列化,獲取物件,以及防止方式

import java.io.ObjectStreamException;  
import java.io.Serializable;  
  
/** 
 * 懶漢式: 
 *      全域性變數初始化放到了例項化方法中,延遲產生物件。 
 *      但是當多個執行緒統一訪問時,有可能出現執行緒不安全的情況。需要優化。 
 * @author 碼歌老薛 
 * @date 建立時間 猴年馬月 
 * @version 1.0 
 */  
public class SingleLazy implements Serializable{  
    //提供靜態的全域性變數 作為訪問該類例項的入口 但是這裡不立即載入  
   private static SingleLazy sh = null;  
      
      
    /** 
     * 構造器私有 無法建立物件 
     */  
    private SingleLazy(){  
       if(sh!=null){  
            throw new RuntimeException();  
        }  
        System.out.println("建構函式被呼叫了");  
    }  
      
    /** 
     * 對外提供get方法獲取 該類的例項 
     * @return 
     * @throws InterruptedException  
     */  
    public static synchronized SingleLazy getInstance() {  
        if(sh==null){  
            sh = new SingleLazy();  
        }  
        return sh;  
          
    }  
      
    private Object readResolve()throws ObjectStreamException{  
        return sh;  
    }  
      
}  

上海尚學堂java培訓 shsxt.com

三、用法總結

1、懶漢式效率是最低的。
2、佔用資源少 不需要延時載入 列舉優於 餓漢式
3、佔用資源比較多 需要延時載入 靜態內部類 優於 懶漢式

更多Java技術文章歡迎閱讀上海尚學堂Java培訓,免費試學和線上公開課培訓課程等你學習。

相關文章