『單例模式』是一種建立型的設計模式,保證一個類只有一個例項,並提供一個訪問它的全域性訪問點。
在一個系統中,一個類經常會被使用在不同的地方,通過單例模式,我們可以避免多次建立多個例項,從而節約系統資源。
單例模式往往有三個特徵,一個類只能有一個例項,它必須自行提供例項的建立,它必須提供方法暴露此例項。
餓漢方式
餓漢總是一次吃個飽,所以這種方式總是在系統初始化的時候建立所有的物件,不管會不會何時被使用。
public class SingleTon {
//自行構造例項
private static final SingleTon instance = new SingleTon();
//置空構造器,不允許外部構造
public SingleTon(){}
//對外暴露內部例項
public SingleTon getInstance(){
return this.instance;
}
}
餓漢方式實現的單例模式是極其簡單的,但缺點也很明顯,即便這個類一時半會不會被使用到,但也必須在編譯的時候初始化分配堆記憶體,建立這個內部例項。
懶漢方式
懶漢很懶,只有在系統用到某個類的例項的時候,才會例項化出一個唯一例項。
public class SingleTon {
private static SingleTon instance= null;
private SingleTon(){}
public static SingleTon getInstance(){
if(null == instance){
instance = new SingleTon();
}
return instance;
}
}
instance 在類編譯的時候沒有初始化,而只有在呼叫 getInstance 方法的時候,才會去例項化 instance。
looks pretty!
多執行緒環境下,執行緒 A 和執行緒 B 同時判斷 instance==null,都去例項化 instance,導致 instance 被例項化兩次,堆中產生一個無引用物件,併發量大的情況下,會有更多的無用物件被建立,甚至可能提前觸發 GC。
懶漢方式優化一(加本地鎖)
執行緒不安全,相信你第一時間也會想到加鎖控制,那你是不是也這麼加的呢?
public class SingleTonLock {
private static SingleTonLock instance= null;
public SingleTonLock(){}
public synchronized SingleTonLock getInstance(){
if (instance == null){
instance = new SingleTonLock();
}
return instance;
}
}
這種方式直接給 getInstance 方法加鎖了,很明顯,會造成大量無效的鎖等待,繼續優化。
public class SingleTonLock {
private static volatile SingleTonLock instance= null;
public SingleTonLock(){}
public SingleTonLock getInstance(){
if (instance == null){
synchronized(this){
//再次判斷是為了防止有的執行緒醒來以後再次例項化
//有可能其他執行緒已經例項化完成了
if (instance == null){
instance = new SingleTonLock();
}
}
}
return instance;
}
}
給 instance 加 volatile 修飾是為了防止 jvm 指令重排序,通過再次判斷可以保證此例項的唯一例項化。
這的確是一種不錯的懶漢例項,推薦大家使用,但我更推薦下一種。
懶漢方式優化二(列舉類)
個人認為使用列舉類實現懶漢單例模式是最佳實踐,列舉類本質上是用靜態欄位來實現的,例如:
public enum Color {
RED(), GREEN(), BLUE(), YELLOW();
}
javap 反編譯這個列舉類得到:
public final class com.example.test.lazy.Color extends java.lang.Enum<com.example.test.lazy.Color> {
public static final com.example.test.lazy.Color RED;
public static final com.example.test.lazy.Color GREEN;
public static final com.example.test.lazy.Color BLUE;
public static final com.example.test.lazy.Color YELLOW;
public static com.example.test.lazy.Color[] values();
public static com.example.test.lazy.Color valueOf(java.lang.String);
static {};
}
那麼,列舉如何實現單例模式,上程式碼:
public class SingleTonE {
public static SingleTonE getInstance(){
return SingleTonEnum.SINGLETON.getInstance();
}
private enum SingleTonEnum{
SINGLETON;
private SingleTonE instance;
SingleTonEnum(){
instance = new SingleTonE();
}
public SingleTonE getInstance(){
return this.instance;
}
}
}
只有當呼叫 getInstance 方法獲取例項的時候,才會觸發列舉類的載入,然後按照上面說的,生成一個靜態欄位並初始化其內部的單例 instance,因為 jvm 保證只能一個執行緒進行類載入,所以整個過程看起來非常的簡單。
個人認為,列舉類實現單例模式是一種最佳實踐,推薦你應用到自己的專案。
近期會整理一個設計模式系列,分別講講 23 種設計模式,感興趣的可以關注下哦~