設計模式-單例模式

1 單例模式簡介

1.1 定義

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

1.2 爲什麼要用單例模式呢?

  簡單來說使用單例模式可以帶來下面幾個好處:

  •   對於頻繁使用的對象,可以省略創建對象所花費的時間,這對於那些重量級對象而言,是非常可觀的一筆系統開銷;

  •  由於 new 操作的次數減少,因而對系統內存的使用頻率也會降低,這將減輕 GC 壓力,縮短 GC 停頓時間;

比如說:線程池、緩存、對話框、註冊表、日誌對象、充當打印機、顯卡等設備驅動程序的對象。

3.爲什麼不使用全局變量確保一個類只有一個實例?

如果說這個對象非常消耗資源,而且程序某次的執行中一直沒用,這樣就造成了資源的浪費。利用單例模式的話,我們就可以實現在需要使用時才創建對象,這樣就避免了不必要的資源浪費。 不僅僅是因爲這個原因,在程序中我們要儘量避免全局變量的使用,大量使用全局變量給程序的調試、維護等帶來困難。

2 單例的模式的實現

通常單例模式在Java語言中,有兩種構建方式:

  • 餓漢方式。指全局的單例實例在類裝載時構建

  • 懶漢方式。指全局的單例實例在第一次被使用時構建。

不管是那種創建方式,它們通常都存在下面幾點相似處:

  • 單例類必須要有一個 private 訪問級別的構造函數,只有這樣,才能確保單例不會在系統中的其他代碼內被實例化;

  • instance 成員變量和 uniqueInstance 方法必須是 static 的。

2.1 餓漢方式(線程安全)


public class Singleton {
   //在靜態初始化器中創建單例,保證了線程安全
  private static Singleton uniqueInstance= new Singleton();
  private Singleton(){}
  public static Singleton getInstance() {
  return uniqueInstance;
}
}
所謂 “餓漢方式” 就是說JVM在加載這個類時就馬上創建此唯一的單例實例,不管你用不用,先創建了再說,如果一直沒有被使用,便浪費了空間,典型的空間換時間,每次調用的時候,就不需要再判斷,節省了運行時間。


2.2 懶漢式(非線程安全和synchronized關鍵字線程安全版本 )


public class Singleton {
	private static Singleton uniqueInstance;
	private Singletion() {
	}
	public static Singleton getInstance() {
		//檢查實例,如果不存在,就進入同步代碼塊
		if (uniqueInstance == null){
		    uniqueInstance = new Singletion();
		}
		return uniqueInstance;
	}
}

所謂 “餓漢方式” 就是說單例實例在第一次被使用時構建,而不是在JVM在加載這個類時就馬上創建此唯一的單例實例。

但是上面這種方式很明顯是線程不安全的,如果多個線程同時訪問getInstance()方法時就會出現問題。如果想要保證線程安全,一種比較常見的方式就是在getInstance() 方法前加上synchronized關鍵字,如下:

public static synchronized Singleton getInstance(){
        if(uniqueSingleton == null){
            uniqueSingleton = new Singleton();
         } 
         return uniqueSingleton;
}

在程序中每次使用getInstance() 都要經過synchronized加鎖這一層,這難免會增加getInstance()的方法的時間消費,而且還可能會發生阻塞。我們下面介紹到的 雙重檢查加鎖版本 就是爲了解決這個問題而存在的。

2.3 懶漢式(雙重檢查加鎖版本)

利用雙重檢查加鎖(double-checked locking),首先檢查是否實例已經創建,如果尚未創建,“才”進行同步。這樣以來,只有一次同步,這正是我們想要的效果。




public class Singleton {
	//volatile保證,當uniqueInstance變量被初始化成Singleton實例時,多個線程可以正確處理uniqueInstance變量
	private volatile static Singleton uniqueInstance;
	private Singletion() {
	}
	public static Singleton getInstance() {
		//檢查實例,如果不存在,就進入同步代碼塊
		if (uniqueInstance == null){
			//只有第一次才徹底執行這裏的代碼
			synchronized(Singleton.class) {
				//進入同步代碼塊後,再檢查一次,如果仍是null,才創建實例
				if(uniqueInstance == null){
					uniqueInstance = new Singletion();
				}
			}
		}
		return uniqueInstance;
	}
}

很明顯,這種方式相比於使用synchronized關鍵字的方法,可以大大減少getInstance() 的時間消費。

2.4 其他方式(枚舉)

除了上面說的幾種創建方式之外,還有挺多種其他的創建方式這裏稍微多提一點使用枚舉的方式,其他創建方式我們就不管了,沒有什麼實質性的作用。

枚舉實現單例的優點就是簡單,但是大部分應用開發很少用枚舉,可讀性並不是很高。個人感覺懶漢式(雙重檢查加鎖版本)還是使用挺多的,這種方式的可讀性也比較好。

public enum Singleton {
    //定義一個枚舉的元素,它就是Singleton的一個實例
    INSTANCE;
    public void doSomeThing() {
        System.out.println("枚舉方法實現單例");
    }
}

使用方法:

public class ESTest{
   public static void main(String[] args){
       Singleton singleton = Singleton.INSTANCE;
       singleton.doSomeThing();
    }
}

2.5 總結

我們主要介紹到了以下幾種方式實現單例模式:

  • 餓漢方式(線程安全)

  • 懶漢式(非線程安全和synchronized關鍵字線程安全版本)

  • 懶漢式(雙重檢查加鎖版本)

  • 枚舉方式


發佈了3 篇原創文章 · 獲贊 3 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章