設計模式-1.單例模式

一、什麼是單例模式
單例模式是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱爲單例類的特殊類。通過單例模式可以保證系統中個類只有一個實例而且該實例易於外界訪問,從而方便對實例個數的控制並節約系統資源。如果希望在系統中某個類的對象只能存在一個,單例模式是最好的解決方案。
   

 二、爲什麼要使用單例模式
    
      對於系統中的某些類來說,只有一個實例很重要,例如,一個系統中可以存在多個打印任務,但是只能有一個正在工作的任務;一個系統只能有一個窗口管理器或文件系統; 一個系統只能有一個計時工具或ID(序號)生成器。如在Windows中就只能打開一個任務管理器。如果不使用機制對窗口對象進行唯一化,將彈出多個窗口, 如果這些窗口顯示的內容完全一致,則是重複對象,浪費內存資源;如果這些窗口顯示的內容不一致,則意味着在某一瞬間系統有多個狀態,與實際不符,也會給用戶帶來誤解,不知道哪一個纔是真實的狀態。因此有時確保系統中某個對象的唯一性即一個類只能有一個實例非常重要。如何保證一個類只有一個實例並且這個實例易於被訪問呢?定義一個全局變量可以確保對象隨時都可以被訪問,但不能防止我們實例化多個對象。一個更好的解決辦法是讓類自身負責保存它的唯一實例。這個類可以保證沒有其他實例被創建,並且它可以提供一個訪問該實例的方法。這就是單例模式的模式動機。

 三、常見的單例模式

    1、有頻繁實例化然後銷燬的情況,也就是頻繁的 new 對象,可以考慮單例模式
    2、創建對象時耗時過多或者耗資源過多,但又經常用到的對象
    3、頻繁訪問 IO 資源的對象,例如數據庫連接池或訪問本地文件

    舉例:

    1.網站在線人數統計,其實就是全局計數器,也就是說所有用戶在相同的時刻獲取到的在線人數數量都是一致的。要實現這個需求,計數器就要全局唯一,也就正好可以用單例模式來實現。
    當然這裏不包括分佈式場景,因爲計數是存在內存中的,並且還要保證線程安全。

   

	public class Counter {
		   
	    private static class CounterHolder{
	        private static final Counter counter = new Counter();
	    }
	 
	    private Counter(){
	        System.out.println("init...");
	    }
	 
	    public static final Counter getInstance(){
	        return CounterHolder.counter;
	    }
	 
	    private AtomicLong online = new AtomicLong();
	 
	    public long getOnline(){
	        return online.get();
	    }
	 
	    public long add(){
	        return online.incrementAndGet();
	    }
	}
	

2.配置文件訪問類,項目中經常需要一些環境相關的配置文件,比如短信通知相關的、郵件相關的。比如 properties 文件,這裏就以讀取一個properties 文件配置爲例,如果你使用的 Spring ,可以用 @PropertySource 註解實現,默認就是單例模式。如果不用單例的話,每次都要 new 對象,每次都要重新讀一遍配置文件,很影響性能,如果用單例模式,則只需要讀取一遍就好了。
   

	public class SingleProperty {
		 
	    private static Properties prop;
	 
	    private static class SinglePropertyHolder{
	        private static final SingleProperty singleProperty = new SingleProperty();
	    }
	 
	    /**
	    * config.properties 內容是 test.name=kite 
	    */
	    private SingleProperty(){
	        System.out.println("構造函數執行");
	        prop = new Properties();
	        InputStream stream = SingleProperty.class.getClassLoader()
	                .getResourceAsStream("config.properties");
	        try {
	            prop.load(new InputStreamReader(stream, "utf-8"));
	        } catch (IOException e) {
	            e.printStackTrace();
	        }
	    }
	 
	    public static SingleProperty getInstance(){
	        return SinglePropertyHolder.singleProperty;
	    }
	    
	    
	    public String getName(){
	        return prop.get("test.name").toString();
	    }
	 
	    public static void main(String[] args){
	        SingleProperty singleProperty = SingleProperty.getInstance();
	        System.out.println(singleProperty.getName());
	    }
	}    

3.數據庫連接池的實現,也包括線程池。爲什麼要做池化,是因爲新建連接很耗時,如果每次新任務來了,都新建連接,那對性能的影響實在太大。所以一般的做法是在一個應用內維護一個連接池,這樣當任務進來時,如果有空閒連接,可以直接拿來用,省去了初始化的開銷。所以用單例模式,正好可以實現一個應用內只有一個線程池的存在,所有需要連接的任務,都要從這個連接池來獲取連接。如果不使用單例,那麼應用內就會出現多個連接池,那也就沒什麼意義了。如果你使用 Spring 的話,並集成了例如 druid 或者 c3p0 ,這些成熟開源的數據庫連接池,一般也都是默認以單例模式實現的。

四,單例模式的四種實現方式

package com.pats.file.design.Single;

public class Singleton {

//  1、餓漢式(線程安全,調用效率高,但是不能延時加載),不推薦
//  private static Singleton instance = new Singleton();
//  private Singleton() {};
//  public static Singleton getInstance() {
//	  return instance;
//  }
  
  
  //2、懶漢式懶漢模式並沒有考慮線程安全問題,在多個線程可能會併發調用它的getInstance()方法,導致創建多個實例
	
//  private static Singleton instance = null;
//  private Singleton() {};
//  public static synchronized Singleton getInstance(){
//      if(instance==null){
//          instance=new Singleton();
//      }
//      return instance;
//  }
  
   
//3.Double CheckLock實現單例:DCL也就是雙重鎖判斷機制加鎖的懶漢模式,看起來即解決了線程併發問題,又實現了延遲加載,然而它存在着性能問題,依然不夠完美。
		
//   	public static volatile  Singleton instance = null;
//    private Singleton(){};
//    public static Singleton getInstance() {
//    	if(instance == null) {
//    		synchronized (Singleton.class) {
//			 if(instance == null) {
//			 instance = new Singleton();	
//			 }	
//		    }
//    		
//    	}
//         return instance;	
//    }
	
	
	//4.通過靜態內部類來實現
	
//	public static class SingletonHandler{
//   
//		public static Singleton instance = new Singleton();
//     
//	}
//	private Singleton() {};
//	public static Singleton getInstance() {
//		return SingletonHandler.instance;
//	}
	
	//5.最後一種實現方式:枚舉。
	
//    枚舉元素本身就是單例
//    INSTANCE;
//     
//    //添加自己需要的操作
//    public void singletonOperation(){     
//    }

}

   上面提到的四種實現單例的方式都有共同的缺點:

  1)需要額外的工作來實現序列化,否則每次反序列化一個序列化的對象時都會創建一個新的實例。

   2)可以使用反射強行調用私有構造器(如果要避免這種情況,可以修改構造器,讓它在創建第二個實例的時候拋異常)。

 而枚舉類很好的解決了這兩個問題,使用枚舉除了線程安全和防止反射調用構造器之外,還提供了自動序列化機制,防止反序列化的時候創建新的對象。因此,《Effective Java》作者推薦使用的方法。不過,在實際工作中,很少看見有人這麼寫。

    
    
總結

 本文總結了五種Java中實現單例的方法,其中前兩種都不夠完美,雙重校驗鎖和靜態內部類的方式可以解決大部分問題,平時工作中使用的最多的也是這兩種方式。枚舉方式雖然很完美的解決了各種問題,但是這種寫法多少讓人感覺有些生疏。個人的建議是,在沒有特殊需求的情況下,使用第三種和第四種方式實現單例模式。

參考:https://blog.csdn.net/zxl646801924/article/details/86999584

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章