類和對象的關係很容易理解,對象就是類的一個具體實例。我們每個類可以創建多個對象,這些對象是相互獨立不同的對象。但是有的時候我們的某個類只希望創建一個對象,不需要創建多個對象。比如框架加載在讀取配置文件的時候,讀取並保存配置文件信息的對象我們沒必要創建多個,只要有一個對象讀取到配置信息並保存在對象中,然後所有要使用到配置信息的地方都使用這個對象就可以了。如果有很多個對象讀出來的配置是一樣一樣的,那麼是完全沒必要的,除了浪費資源。再比如我們的工具類、線程池、緩存、日誌對象等等。對於這樣的一些類,我們只希望永遠有且只有一個對象的實例,那麼我們就可以使用單例模式去創建實例。單例顧名思義就是隻有一個實例,對於單例模式我們通常又有兩種單例模式,分別是餓漢模式和懶漢模式。
1、餓漢單例:在類初始化的時候就加載
//餓漢單例模式
public class Singleton1 {
// 定義一個單例類的實例對象instance,同時實例初始化,一定要用static修飾
private static Singleton1 instance = new Singleton1();
// 構造器是私有的,只能在該類中被使用,注意使用private修飾
private Singleton1() {
}
// 對外提供一個訪問獲取該對象實例的方法,返回之前已經創建的實例
public static Singleton1 getInstance() {
return instance;
}
}
2、懶漢單例:只有在第一次用到的時候纔會去加載
//懶漢單例模式
public class Singleton2 {
// 定義一個單例類的實例對象instance,但是暫時不創建該對象的實例,一定要用static修飾
private static Singleton2 instance = null;
// 構造器是私有的,只能在該類中被使用,注意使用private修飾
private Singleton2() {
}
// 對外提供一個訪問獲取該對象實例的方法,返回上面定義的實例對象
public static Singleton2 getInstance() {
// 因爲上面的實例對象在類初始化的時候並沒有創建,所以在此處判斷如果還沒有實例化,就創建一個實例
if (instance == null) {
instance = new Singleton2();
}
// 返回實例的時候,一定是不爲空的
return instance;
}
}
餓漢模式是線程安全的,而懶漢模式因爲在用到的時候纔去加載,所以可能在多線程中同事請求創建實例,所以這就導致懶漢模式加載是線程不安全的。因此我們一般在使用懶漢模式的時候,會加入多線程控制,以多線程下也是正常的單例模式。代碼寫法一般有兩種,一種是在方法上加入synchronized,另一種是將創建代碼放在synchronized包裹的代碼塊中:
// 加入synchronized目的是防止多線程下創建多個實例,那樣其實就不是單例了
public static synchronized Singleton2 getInstance() {
if (instance == null) {
instance = new Singleton2();
}
return instance;
}
對於上面兩種單例模式都有各自的缺陷,惡漢模式沒有延遲加載,導致的結果可能是在初始化的時候,消耗太多資源。懶漢模式雖然延遲加載了,但是爲了線程安全導致效率降低。
3、餓漢改版延遲加載實例:使用靜態內部類延遲加載
//內部類獲得餓漢單例模式
public class Singleton3 {
// 定義一個內部類,該內部類持有一個外部類的實例對象
private static class SingletonHolder {
private final static Singleton3 INSTANCE = new Singleton3();
}
// 構造器是私有的,只能在該類中被使用,注意使用private修飾
private Singleton3() {
}
// 對外提供一個訪問獲取該對象實例的方法
public static Singleton3 getInstance() {
// 通過內部類對象獲取單例實例對象
Singleton3 instance = SingletonHolder.INSTANCE;
return instance;
}
}
這樣做的目的是在類初始化的時候並不加載內部類對象,因爲內部類沒有被使用所以是不會加載的。當獲取單例實例的時候,內部類會去實例化一個單例對象,然後返回給調運放,這樣既保證了線程安全,也保證了延遲加載。如果在不要求延遲加載的情況下,建議使用第一種餓漢模式,如果在要求延遲加載的情況下,建議使用第三種以保證效率。
單例模式還有兩種創建方式,一種是通過枚舉,一種是通過volatile關鍵字雙重校驗,不過個人覺得很少有人用這兩種方式。