面向物件設計模式之---單例模式(Singleton Pattern)
我們知道類可以用來例項化物件,而物件可以被例項化多個,但是有些時候我們並不想例項化多個物件,只希望只有一個,例如做一個窗體應用程式,有一個按鈕,點選一下可以出現選單,再點選一下就不會彈出同樣的一個選單,比如Word中我們多次點選替換按鈕的時候只會出現一個視窗。
這樣的只允許出現一個實體的設計模式,被形象地稱為單例模式,單例即單個例項。
單例模式的定義如下:
保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。
——《大話設計模式》
它的UML類圖十分簡單
由圖可知,在只允許出現一個例項的類中,維護著一個私有靜態類例項物件,這個類的構造方法也是私有
下面的程式碼演示了最原始的單例模式,其中,ToolBox類只允許出現一次。
以下所有程式碼基於Java語言編寫。
class ToolBox
{
private static ToolBox instance;
private ToolBox(){}
public static ToolBox getInstance ()
{
if(instance == null)
{
System.out.println("初始化了。。。");
instance = new ToolBox();
}
return instance;
}
}
public class Main
{
public static void main(String[] args)
{
ToolBox tb = ToolBox.getInstance();
ToolBox tb1 = ToolBox.getInstance();
}
}
執行結果如下:
顯然,21、22行在兩次嘗試獲得ToolBox物件的時候,該類只被例項化了一次。
最後我們不得不考慮一下多執行緒問題,如果不考慮的話,還是可能會建立多個例項的。或許我們會這麼寫
class ToolBox
{
private static ToolBox instance;
private ToolBox(){}
public static synchronized ToolBox getInstance()
{
if(instance == null)
{
System.out.println("初始化了。。。");
instance = new ToolBox();
}
else
{
System.out.println("物件存在。。。");
}
return instance;
}
}
class ThreadHome implements Runnable
{
public void run()
{
System.out.println("當前執行緒:"+Thread.currentThread().getName()+"訪問");
ToolBox tb = ToolBox.getInstance();
}
}
public class Main
{
public static void main(String[] args)
{
ThreadHome th = new ThreadHome();
Thread t1 = new Thread(th,"執行緒1");
Thread t2 = new Thread(th,"執行緒2");
t1.start();
t2.start();
}
}
雖然上面使用了同步方法,多個執行緒競爭時只允許一個執行緒進入getInstance(),但是試想,計算機的CPU是為每一個執行緒分配時間片的,如果在規定的時間片內沒有執行完成,則會進入就緒狀態,等待再次獲取時間片,假設同時有兩個執行緒嘗試獲取ToolBox類,我們就暫定為執行緒1和執行緒2吧。執行緒1在執行到instance = new ToolBox();
前失去CPU時間,而執行緒2完整運行了getInstance(),此時,執行緒1被喚醒,執行完getInstance(),這樣,系統中就會存在兩個ToolBox物件了。
上面這種方式還有一個弊端就是執行緒每次構造物件都需要加同步鎖,這將從某個方面降低系統性能,所以我們不妨做一個改進,就對構造物件的語句加同步鎖,在構造物件之前,再次進行一次instance是否為空的判斷,這樣就可以防止上面所分析的多執行緒問題。這種方式叫做雙重鎖定,同時也被叫做懶漢式單例類,與之對應的叫餓漢式單例類,不同之處在於,餓漢式單例類是利用靜態初始化的方式在自己載入時就將自己例項化,懶漢式單例類是在第一次被引用時才會使用,比較懶嘛,等要用的時候才動身。
上面程式碼修改如下:
class ToolBox
{
private static ToolBox instance;
private ToolBox(){}
public static ToolBox getInstance()
{
if(instance == null)
{
synchronized(ToolBox.class)
{
if(instance == null)
{
System.out.println("初始化了。。。");
instance = new ToolBox();
}
}
}
else
{
System.out.println("物件存在。。。");
}
return instance;
}
}
class ThreadHome implements Runnable
{
public void run()
{
System.out.println("當前執行緒:"+Thread.currentThread().getName()+"訪問");
ToolBox tb = ToolBox.getInstance();
}
}
public class Main
{
public static void main(String[] args)
{
ThreadHome th = new ThreadHome();
Thread t1 = new Thread(th,"執行緒1");
Thread t2 = new Thread(th,"執行緒2");
t1.start();
t2.start();
}
}
執行結果如下:
參考資料:
淺淡java單例模式結合多執行緒測試 –部落格園