設計模式解密(1)- 單例模式


1、前言

1-1、 概述

       設計模式 = 某類特定問題的解決方案,那么單例模式是解決什么問題的解決方案呢?

  定義:保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。

  含義:單例  =  一個實例

  解決的問題:在任何時間內只有一個類實例存在的模式

  解決方法:保證一個類只有一個實例化對象,並提供一個全局訪問入口

  本質:控制實例的數量

  注意:要合理的使用單例,避免單例成為瓶頸

  英文:Singleton

    類型:創建類模式

1-2、問題引入

模擬網站訪問數量統計功能:

package com.designmode.singleton;

/**
 * 網站計數器
 */
class WebCounter {
    private int count = 0;

	public int getCount() {
		return count;
	}
	public void setCount(int count) {
		this.count = count;
	}
}

/**
 * 用戶訪問
 */
class Visitor{
	public WebCounter webCounter;
	public Visitor(WebCounter mwebCounter){
		webCounter = mwebCounter;
	}
	//訪問
	public void visit(){
		webCounter.setCount(webCounter.getCount()+1);;
	}
}
/**
 * 模擬用戶訪問網站
 */
public class SingleTest{
	public static void main(String[] args){
		WebCounter webCounter1 = new WebCounter();
		WebCounter webCounter2 = new WebCounter();
		Visitor visitor1 = new Visitor(webCounter1);
		Visitor visitor2 = new Visitor(webCounter2);

        System.out.println("是不是同一個網站?");
        if(webCounter1.equals(webCounter2)){
            System.out.println("是");
        }else {
            System.out.println("不是");
        }
        //visitor1訪問該網站
        visitor1.visit();
        System.out.println("訪問量:" + webCounter1.getCount());
        //visitor2訪問該網站
        visitor2.visit();
        System.out.println("訪問量:" + webCounter2.getCount());
    }
}

結果:

是不是同一個網站?
不是
訪問量:1
訪問量:1

從結果看,兩個人訪問的不是一個網站實例,其實我們要實現的邏輯是,訪問同一個網站,計算訪問量,這顯然是不符合我們想要的

 

2、介紹

 2-1、分析引入的問題

  沖突:從上面的結果可以看出,網站計數器類操作的明顯不是同一個實例

  目標:所有訪問者操作同一個網站計數器類

  單例模式就是為了解決這類問題的解決方案:實現一個類只有一個實例化對象,並提供一個全局訪問入口

 

 2-2、解決引入的問題

解決:改造一下網站計數器類的實現代碼

package com.designmode.singleton;

/**
 * 網站計數器
 */
class WebCounter {
    private int count = 0;
   
    private static WebCounter instance  = new  WebCounter();
    private WebCounter() {
    }
    public static WebCounter getInstance() {
       return instance;
    }
    
  public int getCount() {
	return count;
  }
  public void setCount(int count) {
	this.count = count;
  }
}
/**
 * 用戶訪問
 */
class Visitor{
	public WebCounter webCounter;
	public Visitor(WebCounter mwebCounter){
		webCounter = mwebCounter;
	}
	//訪問
	public void visit(){
		webCounter.setCount(webCounter.getCount()+1);;
	}
}
/**
 * 模擬用戶訪問網站
 */
public class SingleTest{
	public static void main(String[] args){
		WebCounter webCounter1 = WebCounter.getInstance();
		WebCounter webCounter2 = WebCounter.getInstance();
		Visitor visitor1 = new Visitor(webCounter1);
		Visitor visitor2 = new Visitor(webCounter2);
		
        System.out.println("是不是同一個網站?");
        if(webCounter1.equals(webCounter2)){
            System.out.println("是");
        }else {
            System.out.println("不是");
        }
        //visitor1訪問該網站
        visitor1.visit();
        System.out.println("訪問量:" + webCounter1.getCount());
        //visitor2訪問該網站
        visitor2.visit();
        System.out.println("訪問量:" + webCounter2.getCount());
    }
}

再來看一下結果:

是不是同一個網站?
是
訪問量:1
訪問量:2

這次是對的!!!

 

 2-3、實現原理

引入單例模式:一般實現方式

public class Singleton {
	//1. 創建私有變量 instance(用以記錄 Singleton 的唯一實例)
	//2. 內部進行實例化
    private static Singleton instance  = new  Singleton();
	//3. 把類的構造方法私有化,不讓外部調用構造方法實例化
    private Singleton() {
    }
	//4. 定義公有方法提供該類的全局唯一訪問點
	//5. 外部通過調用getInstance()方法來返回唯一的實例
    public static Singleton getInstance() {
        return instance;
    }
}

 

2-4、優點、缺點

優點:
  1.在單例模式中,活動的單例只有一個實例,對單例類的所有實例化得到的都是相同的一個實例。這樣就防止其它對象對自己的實例化,確保所有的對象都訪問一個實例
  2.單例模式具有一定的伸縮性,類自己來控制實例化進程,類在實例化進程上有相應的伸縮性
  3.提供了對唯一實例的訪問入口
  4.由於在系統內存中只存在一個對象,因此可以節約系統資源,當需要頻繁創建和銷毀的對象時單例模式無疑可以提高系統的性能
  5.允許可變數目的實例(可以根據實際情況需要,在單例模式的基礎上擴展做出雙例模式、多例模式)
  6.避免對共享資源的多重占用
缺點:
  1.不適用於變化的對象,如果同一類型的對象總是要在不同的用例場景發生變化,單例就會引起數據的錯誤,不能保存彼此的狀態
  2.由於單利模式中沒有抽象層,因此單例類的擴展有很大的困難。
  3.單例類的職責過重,在一定程度上違背了“單一職責原則”
  4.濫用單例將帶來一些負面問題,如為了節省資源將數據庫連接池對象設計為的單例類,可能會導致共享連接池對象的程序過多而出現連接池溢出;如果實例化的對象長時間不被利用,系統會認為是垃圾而被回收,這將導致對象狀態的丟失

 

3、實現

3-1、餓漢模式、懶漢模式

餓漢模式:

package com.designmode;

/**
 * 餓漢模式(最簡單的形式)
 */
public class Singleton {
    private static  Singleton instance  = new  Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return instance;
    }
}

應用場景:

  要求直接在應用啟動時加載並初始化
  單例對象要求初始化速度非常快

 

懶漢模式:

package com.designmode;

/**
 * 懶漢模式(最簡單的形式)
 */
public class Singleton {
	private static  Singleton instance  = null;

    private Singleton() {
    }

    public static Singleton newInstance() {
	    if(instance == null){
	    	instance = new Singleton();
	    }
        return instance;
    }
}

 應用場景:

  單例初始化的操作耗時比較長(可以相應縮短應用啟動時間)
  單例只是在某個特定場景的情況下才會被使用,即按需延遲加載單例

 

對比:

  餓漢式:自動進行單例的初始化
  懶漢式:有需要的時候才手動調用getInstance()進行單例的初始化操作

3-2、多線程下的實現

在多線程的情況下:

  “餓漢式單例模式”:適用,因為JVM只會加載一次單例類
  “懶漢式單例模式”:不適用,因為“懶漢式”在創建單例時是線程不安全的,多個線程可能會並發調用 getInstance 方法從而出現重復創建單例對象的問題

下面有幾個解決方案:

方案1:同步鎖

//使用同步鎖 synchronized (Singleton.class) 防止多線程同時進入造成instance被多次實例化
package com.designmode;

public class Singleton {
	private static  Singleton instance  = null;

    private Singleton() {
    }

    public static  Singleton getInstance() {
    	synchronized (Singleton.class){
           if(instance == null){
        	  instance = new Singleton();
           }
    	}
      return instance;
   }
}

  

方案2:雙重校驗鎖

//在同步鎖的基礎上( synchronized (Singleton.class) 外)添加了一層if,這是為了在Instance已經實例化后下次進入不必執行 synchronized (Singleton.class) 獲取對象鎖,從而提高性能
package com.designmode;

public class Singleton {
	private static  Singleton instance  = null;

    private Singleton() {
    }

    public static  Singleton getInstance() {
    	if(instance == null){
    		synchronized (Singleton.class){
    			if(instance == null){
    				instance = new Singleton();
    			}
    		}
    	}
        return instance;
   }
}

  

方案3:靜態內部類

//在JVM進行類加載的時候會保證數據是同步的,我們采用內部類實現:在內部類里面去創建對象實例
//只要應用中不使用內部類 JVM 就不會去加載這個單例類,也就不會創建單例對象,從而實現“懶漢式”的延遲加載和線程安全。
package com.designmode;

public class Singleton {
	//在裝載該內部類時才會去創建單例對象
    private static class Singleton2{
    	private static Singleton instance  = new Singleton();
    }
    
    private Singleton() {
    }

    public static Singleton getInstance() {
        return Singleton2.instance;
    }
}

  

方案4:枚舉類型

//最簡潔、易用的單例實現方式,(《Effective Java》推薦)
package com.designmode;

public enum Singleton{
    //定義一個枚舉的元素,它就是Singleton的一個實例
    INSTANCE;
    private Singleton() {
      
    }
	public void doSomething(){  
        
    }  
}

調用方式:

Singleton.INSTANCE.doSomething();

  

4、總結

      設計模式 = 某類特定問題的解決方案,那么單例模式是解決什么問題的解決方案呢?

  含義:單例  =  一個實例

  解決的問題:在任何時間內只有一個類實例存在的模式

  解決方法:保證一個類只有一個實例化對象,並提供一個全局訪問入口

  本質:控制實例的數量

  注意:要合理的使用單例,避免單例成為瓶頸

 

 

PS:源碼地址   https://github.com/JsonShare/DesignPattern/tree/master

 

PS:原文地址 http://www.cnblogs.com/JsonShare/p/7093947.html

   


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2026 CODEPRJ.COM