單例模式的定義與特點
老弟覺得叨襟,為了節(jié)省內(nèi)存資源续捂,保證數(shù)據(jù)內(nèi)容的全局一致性榨惰,要求某些類只能創(chuàng)建一個實例扯键,這就是所謂的單例模式坏怪。
單例(Singleton)模式的定義:指一個類只有一個實例简烘,且該類能自行創(chuàng)建這個實例的一種模式笑陈。
在計算機系統(tǒng)中放刨,還有 Windows 的回收站笛臣、操作系統(tǒng)中的文件系統(tǒng)云稚、多線程中的線程池、顯卡的驅(qū)動程序?qū)ο笊虮ぁ⒋蛴C的后臺處理服務静陈、應用程序的日志對象、數(shù)據(jù)庫的連接池、網(wǎng)站的計數(shù)器鲸拥、Web 應用的配置對象拐格、應用程序中的對話框、系統(tǒng)中的緩存等常常被設計成單例刑赶。
單例模式有 3 個特點:
1.單例類只有一個實例對象捏浊。
2.該單例對象必須由單例類自行創(chuàng)建。
3.單例類對外提供一個訪問該單例的全局訪問點角撞。
單例模式的結(jié)構(gòu)與實現(xiàn)
單例模式是設計模式中最簡單的模式之一呛伴。通常,普通類的構(gòu)造函數(shù)是公有的谒所,外部類可以通過“new 構(gòu)造函數(shù)()”來生成多個實例热康。但是,如果將類的構(gòu)造函數(shù)設為私有的劣领,外部類就無法調(diào)用該構(gòu)造函數(shù)姐军,也就無法生成多個實例。這時該類自身必須定義一個靜態(tài)私有實例尖淘,并向外提供一個靜態(tài)的公有函數(shù)用于創(chuàng)建或獲取該靜態(tài)私有實例奕锌。
1. 單例模式的結(jié)構(gòu)
單例模式的主要角色如下
單例類:包含一個實例且能自行創(chuàng)建這個實例的類。
訪問類:使用單例的類村生。
單例模式有很多種實現(xiàn)的方式惊暴,例如:懶漢式、惡漢式趁桃、靜態(tài)內(nèi)部類模式和枚舉辽话。但是懶漢式和餓漢式通常不作為我們的技術方案,通常會使用靜態(tài)內(nèi)部類的模式來構(gòu)建單例卫病。
2. 單例模式的實現(xiàn)
2.1 懶漢式
public class LazySingletonUnsafe01 {
private static LazySingletonUnsafe01 INSTANCE;
private LazySingletonUnsafe01(){}
public static LazySingletonUnsafe01 getInstance(){
if ( null == INSTANCE ){ //非原子操作 油啤,線程不安全
// tmp line 01
INSTANCE = new LazySingletonUnsafe01();
}
return INSTANCE;
}
}
懶漢式的單例模式是在類初始化時先不對靜態(tài)變量 INSTANCE 初始化,該類的構(gòu)造器為私有蟀苛,故而獲得該類實例只能通過getInstance方法益咬,在獲得該類實例前判斷是否為空,若為空則創(chuàng)建該實例并返回帜平。代碼看起來沒有什么問題幽告,但是常言道Java天生就是多線程的,不支持并發(fā)的程序不是好碼農(nóng)罕模,這個程序并不是線程安全的评腺。例如,敏感詞表淑掌,如果兩個線程同時執(zhí)行該方法并且同時運行到 “tmp line 01” 蒿讥,就一定會創(chuàng)建兩個實例對象。這違背了單例模式的意義。所以芋绸,在java程序中為了保證線程安全媒殉,要在方法上加上synchronized。當然你也可是使用Lock的實現(xiàn)類ReentranLock來作為synchronized的替代摔敛。
public class LazySingletonSafe01 {
private static LazySingletonSafe01 INSTANCE;
private LazySingletonSafe01(){}
public static synchronized LazySingletonSafe01 getInstance(){
if ( null == INSTANCE ){
INSTANCE = new LazySingletonSafe01();
}
return INSTANCE;
}
}
但是廷蓉,該方案的弊端就是,想要獲得實例就必須先獲取對象的鎖(這里是靜態(tài)方法所以指的是類對象)马昙。程序中如果多處用到該實例桃犬,類似操作的線程都需要申請這個鎖,難免會發(fā)生阻塞行楞,效率低下攒暇。所以就衍生出來雙重校驗的模式。
public class LazySingletonSafe02 {
private volatile static LazySingletonSafe02 INSTANCE;
private LazySingletonSafe02(){}
public static LazySingletonSafe02 getInstance(){//雙重驗證保證安全
if ( null == INSTANCE ){
synchronized (LazySingletonSafe02.class){
if ( null == INSTANCE ){
// tmp line 01
INSTANCE = new LazySingletonSafe02();
}
}
}
return INSTANCE;
}
}
所謂的雙重校驗子房,在該方法的層面只是進行了二次判斷形用,在創(chuàng)建實例后其他線程不再需要獲取鎖來得到實例。雙重校驗的關鍵在于“volatile”证杭,僅僅使用sychronized并不能解決JVM指令重排序的問題田度。
2.2 餓漢式
public class HungrySingleton01 {
private final static HungrySingleton01 INSTANCE = new HungrySingleton01();
private HungrySingleton01(){}
public static HungrySingleton01 getInstance(){
return INSTANCE;
}
}
餓漢式在靜態(tài)變量初始化時賦初值解愤。
public class HungrySingleton02 {
private static HungrySingleton02 INSTANCE ;
static {
INSTANCE = new HungrySingleton02();
}
private HungrySingleton02(){}
public static HungrySingleton02 getInstance(){
return INSTANCE;
}
}
也可以寫成在靜態(tài)初始化塊中初始化靜態(tài)變量镇饺。
2.3 靜態(tài)內(nèi)部類
public class DefaultLabelVocabularyDictionary {
// 日志對象
private static final Logger LOGGER = LoggerFactory.getLogger(DefaultLabelVocabularyDictionary.class);
// 字典容器
private Map<String, String> dictionary;
// 私有構(gòu)造器
private DefaultLabelVocabularyDictionary() {
// mock
dictionary = Collections.UnmodifiableMap(dict);
}
// 靜態(tài)內(nèi)部類,聲明外部類對象送讲,靜態(tài)初始化塊初始化INSTANCE實例兰怠,確保對象唯一。
private static class Holder {
private static final DefaultLabelVocabularyDictionary INSTANCE;
static {
try {
LOGGER.info("To load Default Label Vocabulary Dictionary from path:{}", DICT_FILE_PATH);
INSTANCE = new DefaultLabelVocabularyDictionary();
} catch (Exception e) {
// 此處可以自定義Exception李茫,方便在日志中找到字典初始化異常的信息
throw new IllegalStateException("Loading Default Label Vocabulary Dictionary fail!", e);
}
}
}
public static boolean containsKey(String key) {
return Holder.INSTANCE.dictionary.containsKey(key);
}
public static Map<String, String> getInstance() {
return Holder.INSTANCE.dictionary;
}
}
老弟在實際編碼中使用到的靜態(tài)內(nèi)部類創(chuàng)建單例模式的字典的代碼,對外暴露的只有功能性的方法肥橙,且Collections.UnmodiableMap()方法會把Map包裝成一個不可更改的對象魄宏。
2.4 枚舉
使用枚舉類型能保證枚舉類中的每個對象在全局都是單例的,但是一般枚舉類作為全局的異常存筏、code宠互、基礎類型等,目前在在下的碼歷中并未發(fā)現(xiàn)有使用枚舉做單例的代碼椭坚。但是實現(xiàn)起來很簡單予跌,此處也就不書寫示例代碼了。
3. 單例模式的應用場景
前面分析了單例模式的結(jié)構(gòu)與特點善茎,以下是它通常適用的場景的特點券册。
在應用場景中,某類只要求生成一個對象的時候。
當對象需要被共享的場合烁焙。由于單例模式只允許創(chuàng)建一個對象航邢,共享該對象可以節(jié)省內(nèi)存,并加快對象訪問速度骄蝇。
當某類需要頻繁實例化膳殷,而創(chuàng)建的對象又頻繁被銷毀的時候。
4. 單例模式的擴展
單例模式可擴展為有限的多例(Multitcm)模式九火,這種模式可生成有限個實例并保存在 ArmyList 中赚窃,客戶需要時可隨機獲取。