面試的時候經常會問到Java的單例模式,這道題能很好的考察候選人對知識點的理解程度。單例模式要求在系統運行時,只存在唯一的一個實例對象。
下面我們來詳細剖析一下其中的關鍵知識點,並介紹五種實現方法,以及它們的優缺點。
一、最簡單的方法是在類加載的時候初始化這個單獨的實例。
首先,定義單例類(沒有特別的,就是起個名字):
1 public class Singleton{
其次,需要定義類變量將單例對象保存下來:
1 private static Singleton instance = new Singleton();
這裡需要注意兩點:
private:除了Singleton類內部,其它地方無法訪問該變量;
static:確保是靜態類變量,這樣可以通過類名來訪問,無須實例;
再次,需要顯式定義構造函數,並將其設置為private,這樣在Singleton類之外new的話編譯器就會報錯,即使是Singleton的子類也不行:
1 private Singleton() {}
最後,定義單例對象的獲取方法,
1 public static Singleton getInstance() { 2 return instance; 3 }
同樣需要注意:
public:這樣外部才能調用;
static:這樣外部才能通過類名來訪問,否則獲取不到單例對象;
二、使用懶加載。在方法一的基礎上,我們需要修改幾處代碼:
首先,靜態類變量應該初始化為NULL:
1 private static Singleton instance = NULL;
其次,getInstance()方法需要承擔生成實例的任務:
1 public static Singleton getInstance() { 2 if(instance == NULL) 3 instance = new Singleton(); 4 return instance; 5 }
三、考慮多線程的問題。方法二在多線程情況下仍然有可能產生多個實例,我們需要使用同步來避免多線程問題。如下,在getInstance()方法前使用synchronized關鍵字:
1 public static synchronized Singleton getInstance() {
四、考慮性能問題。synchronized關鍵字修飾getInstance()方法,會導致所有調用getInstance()方法的線程都要競爭同一把鎖,即使在單例對象已經生成好的情況下。這裡使用double check的方式。另外,還需要注意instance在初始化過程中,可能已經不為NULL,這樣有可能會導致程序崩潰,因此這裡加了個臨時變量t,確保初始化完成之後再賦值給instance。
public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null){ Singleton t = new Singleton(); instance = t; } } } return instance; }
五、更簡化的方法。基於靜態內部類的特性,我們可以利用它的懶加載機制簡化實現:
1 public static Singleton getInstance(){ 2 return NestedClass.instance; 3 } 4 5 private static class NestedClass{ 6 private static Singleton instance = new Singleton(); 7 }