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


1、什么是單例模式?

  采取一定的辦法保證在整個軟件系統中,單例模式確保對於某個類只能存在一個實例。有如下三個特點:

  ①、單例類只能有一個實例

  ②、單例類必須自己創建自己的實例

  ③、單例類必須提供外界獲取這個實例的方法

 

2、單例類的設計思想(Singleton)

  ①、外界不能創建這個類的實例,那么必須將構造器私有化。

public class Singleton {
	//構造器私有化
	private Singleton(){
		
	}

}

  ②、單例類必須自己創建自己的實例,不能允許在類的外部修改內部創建的實例,所以將這個實例用 private 聲明。為了外界能訪問到這個實例,我們還必須提供 get 方法得到這個實例。因為外界不能 new 這個類,所以我們必須用 static 來修飾字段和方法。

//在類的內部自己創建實例
	private static Singleton singleton = new Singleton();

	//提供get 方法以供外界獲取單例
	public Singleton getInstance(){
		return singleton;
	}

 

3、單例模式之餓漢模式

public class Singleton {
	//構造器私有化
	private Singleton(){
		
	}
	//在類的內部自己創建實例
	private static Singleton singleton = new Singleton();

	//提供get 方法以供外界獲取單例
	public static Singleton getInstance(){
		return singleton;
	}
	
}

  測試:

public static void main(String[] args) {
	Singleton s1 = Singleton.getInstance();
	Singleton s2 = Singleton.getInstance();
	System.out.println(s1.equals(s2)); //true
}

  

  這種模式避免了多線程的同步問題,不過在 類裝載的時候就進行了實例化,有可能這個實例化過程很長,那么就會加大類裝載的時間;有可能這個實例現階段根本用不到,那么創建了這個實例,也會浪費內存。沒有達到 lazy-loading 的效果。

 

4、單例模式之懶漢模式(線程不安全)

//懶漢模式
public class Singleton {
	//構造器私有化
	private Singleton(){
		
	}
	//在類的內部自己創建實例的引用
	private static Singleton singleton = null;

	//提供get 方法以供外界獲取單例
	public static Singleton getInstance(){
		if(singleton == null){
			singleton = new Singleton();
		}
		return singleton;
	}
	
}

  這種方法達到了 lazy-loading 的效果,即我們在第一次需要得到這個單例的時候,才回去創建它的實例,以后再需要就可以不用創建,直接獲取了。但是這種設計在多線程的情況下是不安全的。

 

  我們可以創建兩個線程來看看這種情況:

public class ThreadSingleton extends Thread{
	@Override
	public void run() {
		try {
			System.out.println(Singleton.getInstance());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		ThreadSingleton s1 = new ThreadSingleton();
		s1.start(); //com.ys.pattern.Singleton@5994a1e9
		
		ThreadSingleton s2 = new ThreadSingleton();
		s2.start(); //com.ys.pattern.Singleton@40dea6bc
	}
}

  很明顯:最后輸出結果的兩個實例是不同的。這便是線程安全問題。那么怎么解決這個問題呢?

  參考這篇博客:Java多線程同步:http://www.cnblogs.com/ysocean/p/6883729.html

 

5、單例模式之懶漢模式(線程安全)

  這里我們采用同步代碼塊來達到線程安全

//懶漢模式線程安全
public class Singleton {
	//構造器私有化
	private Singleton(){
		
	}
	//在類的內部自己創建實例的引用
	private static Singleton singleton = null;

	//提供get 方法以供外界獲取單例
	public static Singleton getInstance() throws Exception{
		synchronized (Singleton.class) {
			if(singleton == null){
				singleton = new Singleton();
			}
		}
		return singleton;
	}
	
}

  

6、單例模式之懶漢模式(線程安全)--雙重校驗鎖

  分析:上面的例子我們可以看到,synchronized 其實將方法內部的所有語句都已經包括了,每一個進來的線程都要單獨進入同步代碼塊,判斷實例是否存在,這就造成了性能的浪費。那么我們可以想到,其實在第一次已經創建了實例的情況下,后面再獲取實例的時候,可不可以不進入這個同步代碼塊?

  

//懶漢模式線程安全--雙重鎖校驗
public class Singleton {
	//構造器私有化
	private Singleton(){
		
	}
	//在類的內部自己創建實例的引用
	private static Singleton singleton = null;

	//提供get 方法以供外界獲取單例
	public static Singleton getInstance() throws Exception{
		if(singleton == null){
			synchronized (Singleton.class) {
				if(singleton == null){
					singleton = new Singleton();
				}
			}
		}
		return singleton;
	}
	
}

  以上的真的完美解決了單例模式嗎?其實並沒有,請看下面:

 

7、單例模式之最終版

  我們知道編譯就是將源代碼翻譯成機械碼的過程,而Java虛擬機的目標代碼不是本地機器碼,而是虛擬機代碼。編譯原理里面有個過程是編譯優化,就是指在不改變原來語義的情況下,通過調整語句的順序,來讓程序運行的更快,這個過程稱為 reorder。

  JVM 只是一個標准,它並沒有規定有關編譯器優化的內容,也就是說,JVM可以自由的實現編譯器優化。

  那么我們來再來考慮一下,創建一個變量需要哪些步驟?

    ①、申請一塊內存,調用構造方法進行初始化

    ②、分配一個指針指向該內存

  而這兩步誰先誰后呢?也就是存在這樣一種情況:先開辟一塊內存,然后分配一個指針指向該內存,最后調用構造方法進行初始化。

 

  那么針對單例模式的設計,就會存在這樣一個問題:線程 A 開始創建 Singleton 的實例,此時線程 B已經調用了 getInstance的()方法,首先判斷 instance 是否為 null。而我們上面說的那種模型, A 已經把 instance 指向了那塊內存,只是還沒來得及調用構造方法進行初始化,因此 B 檢測到 instance 不為 null,於是直接把  instance 返回了。那么問題出現了:盡管 instance 不為 null,但是 A 並沒有構造完成,就像一套房子已經給了你鑰匙,但是里面還沒有裝修,你並不能住進去。

  解決方案:使用 volatile 關鍵字修飾 instance

  我們知道在當前的Java內存模型下,線程可以把變量保存在本地內存(比如機器的寄存器)中,而不是直接在主存中進行讀寫。這就可能造成一個線程在主存中修改了一個變量的值,而另外一個線程還繼續使用它在寄存器中的變量值的拷貝,造成數據的不一致。

  volatile修飾的成員變量在每次被線程訪問時,都強迫從共享內存中重讀該成員變量的值。而且,當成員變量發生變化時,強迫線程將變化值回寫到共享內存。這樣在任何時刻,兩個不同的線程總是看到某個成員變量的同一個值。

//懶漢模式線程安全--volatile
public class Singleton {
	//構造器私有化
	private Singleton(){
		
	}
	//在類的內部自己創建實例的引用
	private static volatile Singleton singleton = null;

	//提供get 方法以供外界獲取單例
	public static Singleton getInstance() throws Exception{
		if(singleton == null){
			synchronized (Singleton.class) {
				if(singleton == null){
					singleton = new Singleton();
				}
			}
		}
		return singleton;
	}
	
}

 

到此我們完美的解決了單例模式的問題。但是 volatile  關鍵字是 JDK1.5 才有的,也就是 JDK1.5 之前是不能這樣用的

 

PS:我們還可以使用 枚舉類型 或靜態內部類來實現單例模式

8、單例模式之枚舉類

public enum Singleton{
    INSTANCE;
    private Singleton(){}
}

  

9、單例模式之靜態內部類

public class InnerSingleton {
    private InnerSingleton(){}
    public static InnerSingleton getInstance(){
        return Inner.instance;
    }
    static class Inner{
        static InnerSingleton instance = new InnerSingleton();
    }

    public static void main(String [] args){
        System.out.println(InnerSingleton.getInstance()==InnerSingleton.getInstance());//true
        System.out.println(InnerSingleton.getInstance().equals(InnerSingleton.getInstance()));//true

    }

 

單例模式的應用:

  1、windows 系統的回收站,我們能在任何盤符刪除數據,但是最后都是到了回收站中

  2、網站的計數器,不過不采用單例模式,很難實現同步

  3、數據庫連接池,可以節省打開或關閉數據庫連接所引起的效率損耗,用單例模式來維護,可以大大降低這種損耗。

 

由上可以總結單例模式的應用場景:

  ①、資源共享

  ②、方便資源互相通信  

 

 


免責聲明!

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



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