單例設(shè)計(jì)模式是最簡(jiǎn)單的一種創(chuàng)建型設(shè)計(jì)模式播赁,其提供了創(chuàng)建對(duì)象的最佳實(shí)現(xiàn)颂郎。該模式涉及到一個(gè)單一的類,且該類負(fù)責(zé)創(chuàng)建自己的對(duì)象容为,同時(shí)確保只有一個(gè)對(duì)象被創(chuàng)建乓序。該類提供了一種訪問其唯一對(duì)象的方式寺酪,訪問方式的實(shí)現(xiàn)是單例設(shè)計(jì)模式的核心,涉及到線程安全等問題替劈。
- 為何使用單例模式寄雀?
通常來(lái)說(shuō),我們獲取到一個(gè)對(duì)象實(shí)例陨献,當(dāng)只使用其內(nèi)部提供的方法盒犹,而不關(guān)注其成員變量時(shí),可以使用單例模式眨业。- 節(jié)省內(nèi)存空間:?jiǎn)卫趦?nèi)存中只有一個(gè)實(shí)例對(duì)象急膀,節(jié)省內(nèi)存空間,避免大量創(chuàng)建及銷毀
- 高性能:減少高質(zhì)量資源重復(fù)占用龄捡,可以進(jìn)行全部訪問
單例模式理論
單例模式
- 什么時(shí)候使用單例模式卓嫂?
- 重復(fù)對(duì)象需要頻繁實(shí)例化及銷毀的對(duì)象
- 有狀態(tài)化的工具對(duì)象
- 頻繁訪問數(shù)據(jù)庫(kù)或文件的對(duì)象
單例角色:?jiǎn)卫J街挥幸粋€(gè)單例角色,在單例的內(nèi)部生成一個(gè)對(duì)象實(shí)例聘殖,同時(shí)提供一個(gè)方法用于獲取這個(gè)對(duì)象實(shí)例晨雳。為了避免外界直接創(chuàng)建對(duì)象實(shí)例從而破壞單例模式,通常構(gòu)造函數(shù)都是設(shè)置為私有的奸腺,外部只能通過(guò)方法獲取到唯一的單例對(duì)象餐禁。
單例模式的實(shí)現(xiàn)方式
比較常見的單例實(shí)現(xiàn)方式有如下:
- 餓漢式
- 懶漢式
- 靜態(tài)內(nèi)部類
代碼實(shí)現(xiàn)
- 餓漢式
package singleton.hungry;
/**
* 單例模式:餓漢式實(shí)現(xiàn)方式,類在加載的時(shí)候就創(chuàng)建單例對(duì)象
*/
public class HungrySingleton {
/**
* 定義類靜態(tài)變量洋机,類加載的時(shí)候就已經(jīng)創(chuàng)建好單例對(duì)象
*/
private static final HungrySingleton INSTANCE = new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance(){
return INSTANCE;
}
}
餓漢式在類加載的時(shí)候就已經(jīng)將單例對(duì)象創(chuàng)建好了坠宴,但是如果沒有使用它,一定程度上造成浪費(fèi)绷旗。不過(guò)喜鼓,由于在類加載的時(shí)候單例對(duì)象就創(chuàng)建好了,因此它是線程安全的衔肢。
- 懶漢式
package singleton.lazy;
/**
* 單例模式:懶漢式實(shí)現(xiàn)方式
*/
public class LazySingleton {
private static LazySingleton INSTANCE = null;
private LazySingleton(){}
public static LazySingleton getInstance(){
if (INSTANCE == null){
INSTANCE = new LazySingleton();
}
return INSTANCE;
}
}
懶漢式是在外部調(diào)用類的方法獲取單例對(duì)象時(shí)才會(huì)創(chuàng)建單例對(duì)象庄岖,它不會(huì)想餓漢式那樣造成浪費(fèi)。但是角骤,會(huì)導(dǎo)致線程安全問題隅忿。在高并發(fā)情況下,可能多個(gè)線程都調(diào)用了構(gòu)造方法來(lái)創(chuàng)建對(duì)象邦尊。為了解決這個(gè)線程并發(fā)問題背桐,通常需要加鎖。
- 線程安全版懶漢式
package singleton.safeLazy;
/**
* 單例模式:線程安全的懶漢式實(shí)現(xiàn)
*/
public class SafeLazySingleton {
/**
* 加volatile關(guān)鍵字蝉揍,防止重排序
*/
private static volatile SafeLazySingleton INSTANCE = null;
private SafeLazySingleton(){}
public static SafeLazySingleton getInstance(){
if(INSTANCE == null){
synchronized (SafeLazySingleton.class){
// 雙重校驗(yàn)
if(INSTANCE == null){
INSTANCE = new SafeLazySingleton();
}
}
}
return INSTANCE;
}
}
線程安全版的懶漢式實(shí)現(xiàn)要點(diǎn)是:1. volatile關(guān)鍵字 2. synchronized鎖 3. 雙重校驗(yàn)判空
1. volatile關(guān)鍵字可以防止初始化對(duì)象時(shí)由于編譯器的作用導(dǎo)致指令重排序
一個(gè)對(duì)象初始化的步驟包括:
- 給對(duì)象實(shí)例分配一塊內(nèi)存空間
instance = allocate(SafeLazySingleton.class)
- 針對(duì)分配好的內(nèi)存空間的對(duì)象實(shí)例链峭,執(zhí)行構(gòu)造方法,對(duì)這個(gè)對(duì)象實(shí)例進(jìn)行初始化操作又沾,對(duì)各個(gè)成員變量賦值弊仪,執(zhí)行初始化邏輯熙卡。
invokeConstructor(instance)
- 經(jīng)過(guò)上面兩個(gè)步驟,一個(gè)對(duì)象實(shí)例完成励饵;此時(shí)將
instance
指針指向這塊內(nèi)存空間驳癌,賦值給我們引用類型的變量,讓它指向SafeLazySingleton
對(duì)象的內(nèi)存地址役听。
以上是正常的步驟颓鲜,然而在編譯器重排序的作用下,可能真正執(zhí)行的指令順序是 1-> 3-> 2禾嫉。
所以當(dāng)A線程去創(chuàng)建單例對(duì)象實(shí)例灾杰,進(jìn)行到3步驟,由于重排序熙参,此時(shí)的對(duì)象是殘缺的艳吠;而此時(shí)B線程判斷INSTANCE
對(duì)象為空,于是進(jìn)行了某些操作孽椰,由于是殘缺對(duì)象昭娩,因此會(huì)出錯(cuò)。
2. 雙重校驗(yàn)
第二重校驗(yàn)是避免A黍匾、B兩個(gè)線程依次進(jìn)入了同步代碼塊栏渺,重復(fù)創(chuàng)建單例對(duì)象。
3. synchronized同步鎖
其實(shí)synchronized
可以直接加在方法上锐涯,這樣就不用雙重校驗(yàn)了磕诊,但是這樣的鎖粒度比較大。
- 靜態(tài)內(nèi)部類
package singleton.staticClass;
/**
* 單例模式:靜態(tài)內(nèi)部類
*/
public class StaticInnerClassSingleton {
private StaticInnerClassSingleton(){}
/**
* 通過(guò)靜態(tài)內(nèi)部類加載時(shí)創(chuàng)建單例對(duì)象
*/
private static class Inner{
final static StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
}
/**
* 第一次調(diào)用此方法時(shí)纹腌,觸發(fā)靜態(tài)內(nèi)部類加載從而創(chuàng)建單例對(duì)象
* @return
*/
public static StaticInnerClassSingleton getInstance(){
return Inner.INSTANCE;
}
}
此方法利用了JVM類加載的線程安全實(shí)現(xiàn)單例霎终。