單例模式的七種寫法比較
簡介
單例模式是一種常用的軟體設計模式,其定義是單例物件的類只能允許一個例項存在。
許多時候整個系統只需要擁有一個的全域性物件,這樣有利於我們協調系統整體的行為。比如在某個伺服器程式中,該伺服器的配置資訊存放在一個檔案中,這些配置資料由一個單例物件統一讀取,然後服務程序中的其他物件再通過這個單例物件獲取這些配置資訊。這種方式簡化了在複雜環境下的配置管理。
基本的實現思路
單例模式要求類能夠有返回物件一個引用(永遠是同一個)和一個獲得該例項的方法(必須是靜態方法,通常使用getInstance這個名稱)。
單例的實現主要是通過以下兩個步驟:
- 將該類的構造方法定義為私有方法,這樣其他處的程式碼就無法通過呼叫該類的構造方法來例項化該類的物件,只有通過該類提供的靜態方法來得到該類的唯一例項;
- 在該類內提供一個靜態方法,當我們呼叫這個方法時,如果類持有的引用不為空就返回這個引用,如果類保持的引用為空就建立該類的例項並將例項的引用賦予該類保持的引用。
注意事項
單例模式在多執行緒的應用場合下必須小心使用。如果當唯一例項尚未建立時,有兩個執行緒同時呼叫建立方法,那麼它們同時沒有檢測到唯一例項的存在,從而同時各自建立了一個例項,這樣就有兩個例項被構造出來,從而違反了單例模式中例項唯一的原則。 解決這個問題的辦法是為指示類是否已經例項化的變數提供一個互斥鎖(雖然這樣會降低效率)。
實現
步驟1:建立一個SingleObject類
package com.ybb.entity; public class SingleObject { //建立 SingleObject 的一個物件 private static SingleObject instance=new SingleObject(); //讓建構函式為 private,這樣該類就不會被例項化 private SingleObject(){}; //獲取唯一可用的物件 public static SingleObject getInstance(){ return instance; } public void show(){ System.out.println("heelo word"); } }
步驟2:從 SingleObject類獲取唯一的物件。
package com.ybb.test;
import com.ybb.entity.SingleObject;
public class Test {
public static void main(String[] args) {
SingleObject obj=SingleObject.getInstance();
obj.show();
}
}
單例模式的幾種寫法
1.餓漢式(靜態常量)【可用】
package com.ybb.entity; //餓漢式(靜態常量)【可用】 public class SingleObject2 { private final static SingleObject2 INSTANCE=new SingleObject2(); private SingleObject2(){}; public static SingleObject2 getInstance(){ return INSTANCE; } public void showing(int num){ System.out.println(num+"條資料"); } }
優點:這種寫法比較簡單,就是在類裝載的時候就完成例項化。避免了執行緒同步問題。
缺點:在類裝載的時候就完成例項化,沒有達到Lazy Loading的效果。如果從始至終從未使用過這個例項,則會造成記憶體的浪費。
2.餓漢式(靜態程式碼塊)【可用】
package com.ybb.entity;
//餓漢式(靜態程式碼塊)【可用】
public class SingleObject3 {
private static SingleObject3 instance;
static{
instance=new SingleObject3();
}
private SingleObject3(){};
public static SingleObject3 getInstance(){
return instance;
}
public void show(){
System.out.println("jiolsjuudn");
}
}
這種方式和上面的方式其實類似,只不過將類例項化的過程放在了靜態程式碼塊中,也是在類裝載的時候,就執行靜態程式碼塊中的程式碼,初始化類的例項。優缺點和上面是一樣的。
3.懶漢式(執行緒不安全)【不可用】
package com.ybb.entity;
//懶漢式(執行緒不安全)【不可用】
public class SingleObject1 {
private static SingleObject1 instance;
private SingleObject1(){};
public static SingleObject1 getInstance(){
if (instance==null) {
instance=new SingleObject1();
}
return instance;
}
public String show(){
String str="wo de nv shen";
return str;
}
}
這種寫法起到了Lazy Loading的效果,但是隻能在單執行緒下使用。如果在多執行緒下, 一個執行緒進入了if (singleton == null)判斷語句塊,還未來得及往下執行, 另一個執行緒也通過了這個判斷語句,這時便會產生多個例項。所以在多執行緒環境下不可使用這種方式。
缺點:這種方式是最基本的實現方式,這種實現最大的問題就是不支援多執行緒。因為沒有加鎖 synchronized,所以嚴格意義上它並不算單例模式,這種方式 lazy loading 很明顯,不要求執行緒安全,在多執行緒不能正常工作
4.懶漢式(執行緒安全 同步方法)【不推薦用】
package com.ybb.entity;
//懶漢式(執行緒安全,同步方法)【不推薦用】
public class SingleObject4 {
private static SingleObject4 instance;
private SingleObject4(){};
//對getInstance()方法進行了執行緒同步,每次例項化物件時,保證了執行緒安全
public static synchronized SingleObject4 getInstance(){
if (instance==null) {
instance=new SingleObject4();
}
return instance;
}
public void show(){
System.out.println("wo shi hao ren");
}
}
缺點:效率太低了,每個執行緒在想獲得類的例項時候,執行getInstance()方法都要進行同步。而其實這個方法只執行一次例項化程式碼就夠了,後面的想獲得該類例項,直接return就行了。方法進行同步效率太低要改進。
優點:這種寫法起到了Lazy Loading的效果
5.懶漢式(執行緒安全 同步程式碼塊)【不可用】
package com.ybb.entity;
//懶漢式(執行緒安全,同步程式碼塊)【不可用】
public class SingleObject5 {
private static SingleObject5 instance;
private SingleObject5(){};
public static SingleObject5 getInstance(){
if (instance==null) {
synchronized(SingleObject5.class){
instance=new SingleObject5();
}
}
return instance;
}
public void show(){
System.out.println("liu yi fei wo nv shen");
}
}
由於第四種實現方式同步效率太低,所以摒棄同步方法,改為同步產生例項化的的程式碼塊。但是這種同步並不能起到執行緒同步的作用。跟第3種實現方式遇到的情形一致,假如一個執行緒進入了if (singleton == null)判斷語句塊,還未來得及往下執行,另一個執行緒也通過了這個判斷語句,這時便會產生多個例項。
6.雙重檢查【推薦用】
package com.ybb.entity;
//雙重檢查 【推薦用】
public class SingleObject6 {
private static volatile SingleObject6 instance;
private SingleObject6(){};
public static SingleObject6 getInstance(){
if (instance==null) {
synchronized(SingleObject6.class){
if (instance==null) {
instance=new SingleObject6();
}
}
}
return instance;
}
public void show(){
System.out.println("li ning wo ou xiang");
}
}
Double-Check概念對於多執行緒開發者來說不會陌生,如程式碼中所示,我們進行了兩次if (singleton == null)檢查,這樣就可以保證執行緒安全了。這樣例項化程式碼只用執行一次,後面再次訪問時,判斷if (singleton == null),直接return例項化物件。
優點:執行緒安全;延遲載入;效率較高。
7.靜態內部類【推薦用】
package com.ybb.entity;
//:靜態內部類【推薦用】
public class SingleObject7 {
private SingleObject7() {}
private static class SingletonInstance {
private static final SingleObject7 INSTANCE = new SingleObject7();
}
public static SingleObject7 getInstance() {
return SingletonInstance.INSTANCE;
}
public void show(){
System.out.println("dsjigggggg");
}
}
這種方式跟餓漢式方式採用的機制類似,但又有不同。兩者都是採用了類裝載的機制來保證初始化例項時只有一個執行緒。不同的地方在餓漢式方式是隻要Singleton類被裝載就會例項化,沒有Lazy-Loading的作用,而靜態內部類方式在Singleton類被裝載時並不會立即例項化,而是在需要例項化時,呼叫getInstance方法,才會裝載SingletonInstance類,從而完成Singleton的例項化。
類的靜態屬性只會在第一次載入類的時候初始化,所以在這裡,JVM幫助我們保證了執行緒的安全性,在類進行初始化時,別的執行緒是無法進入的。
優點:避免了執行緒不安全,延遲載入,效率高。