拿出一張A4紙畫出單例模式的UML圖(面試時如果被問到設計模式相關問題,能先畫出UML圖肯定是加分項):
估計很多小夥伴都忘記怎麼讀UML圖了吧?這裡詳細解釋下:
實心箭頭是單向關聯關係:這裡表示定義的成員變量instance是類Singleton自身的一個實例
‘-’減號表示private,這裡的成員變量和構造函數都要用private修飾
‘+’加號表示public 對外暴露方法getInstance(),自然要用public static修飾
單例模式具體實現細節,看英文解釋會更直觀:
1.The implementation involves a static member in the "Singleton" class,
靜態成員變量instance
2.a private constructor
私有構造函數
3.a static public method that returns a reference to the static member.
public static修飾的返回一個實例對象的方法
一段簡單的代碼實現:
public final class Singleton {
//類一加載時實例就被創建
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
當然如果面試時僅僅寫出上面的代碼是遠遠不夠的,面試官不會輕易讓我們過關的,這只是剛剛熱身,更深入的問題還在後面:
1.你知道懶加載嗎?是怎麼用在單例創建上的?有什麼優勢?
如果某個實例的創建(比如數據庫連接池的創建)需要消耗很多系統資源,就需要引入懶加載機制。即上面的代碼在類加載時就創建好了,如果在程序中始終沒用到這個實例就會浪費很多系統資源。
為避免這種情況,就引入了懶加載機制,即在使用這個實例的時候才創建它。
引入懶加載後的單例實現代碼如下(這就是我們常聽到的“懶漢式”單例):
public class Singleton{
private static Singleton instance = null;
private Singleton(){
…
}
public static Singleton getInstance(){
if (instance == null)
//實例在調用getInstance()函數的時候才會創建
instance = new Singleton(); //1. A線程執行
return instance; //2.B線程執行
}
}
2.這個例子你考慮到線程安全了嗎?什麼是線程同步?什麼是雙重檢查鎖定?怎麼用在單例上?
的確當引入多線程時,以上代碼不是線程安全的:假設A線程執行代碼1的同時,B線程執行代碼2。此時,線程A可能會看到instance引用的對象還沒有完成初始化。
通過Java語言的關鍵字synchronized實現線程同步,當它用來修飾一個方法或者一個代碼塊的時候,能夠保證在同一時刻最多隻有一個線程執行該段代碼。因此通過對函數getInstance()做同步處理來實現線程安全的初始化:
class Singleton
{
private static Singleton instance;
private Singleton()
{
...
}
public static synchronized Singleton getInstance()
{
if (instance == null)
instance = new Singleton();
return instance;
}
...
public void doSomething()
{
...
}
}
上面的代碼是線程安全的一種實現,但是如果考慮到性能,synchronization是非常消耗資源的,一次只能有一個線程調用getInstance()方法,而getInstance方法在對象實例化之後無需再阻塞訪問。即如果對象已經創建完成,只需要return 這個對象就OK了,不需要用syncronized同步。
因此進一步優化:先在非synchronized塊檢查object是否為null,是就直接return object,否則進入syncronized同步塊創建object,這就成為雙重鎖機制(double locking mechanism).
代碼實現如下:
//Lazy instantiation using double locking mechanism.
class Singleton
{
private static Singleton instance;
private Singleton()
{
System.out.println("Singleton(): Initializing Instance");
}
public static Singleton getInstance()
{
if (instance == null) //1.第一次檢查
{
synchronized(Singleton.class)//2.加鎖
{
if (instance == null) //3.第2次檢查
{
System.out.println("getInstance(): First time getInstance was invoked!");
instance = new Singleton(); //4.實例化對象
}
}
}
return instance;
}
public void doSomething()
{
System.out.println("doSomething(): Singleton does something!");
}
}