單例模式
單例模式是我們平時(shí)經(jīng)常遇到的設(shè)計(jì)模式之一,它是一種對(duì)象創(chuàng)建模式,用于產(chǎn)生對(duì)象實(shí)例,確保一個(gè)類只實(shí)例化一次。這帶來(lái)的好處是顯而易見(jiàn)的,頻繁的new操作,會(huì)帶來(lái)新建對(duì)象造成的系統(tǒng)開(kāi)銷,尤其是一些重量級(jí)對(duì)象更是如此。
下面是單例模式的基本結(jié)構(gòu)圖:

單例模式常用的幾種實(shí)現(xiàn)方式如下:
- 基于synchronized實(shí)現(xiàn)
- 基于DCL實(shí)現(xiàn)
- 基于靜態(tài)塊的實(shí)現(xiàn)
- 基于靜態(tài)內(nèi)部類實(shí)現(xiàn)
- 基于枚舉的實(shí)現(xiàn)
方法一:基于synchronized實(shí)現(xiàn)
public class SynchronizedSingleton {
private SynchronizedSingleton(){}
private static SynchronizedSingleton instance;
public synchronized static SynchronizedSingleton getInstance(){
if(instance == null){
instance = new SynchronizedSingleton();
}
return instance;
}
}
這是一種最容易理解的解決方案,通過(guò)對(duì)getInstance方法加鎖的方式,確保多線程環(huán)境下instance實(shí)例的唯一性,不過(guò)加鎖帶來(lái)的性能問(wèn)題也是最大的缺陷。
方法二:基于DCL+volatile實(shí)現(xiàn)
public class DCLSingleton {
/** * 私有的構(gòu)造函數(shù) */
private DCLSingleton(){ System.out.println("instance is created!"); }
private static DCLSingleton instance;
public static DCLSingleton getInstance(){
if(instance == null){
synchronized(DCLSingleton.class){
if(instance == null){
instance = new DCLSingleton();
}
}
}
return instance;
}
}
其中** 構(gòu)造函數(shù)必須是private訪問(wèn)級(jí)別的 **,防止單例類不會(huì)被其他代碼通過(guò)構(gòu)造函數(shù)實(shí)例化。其次instance成員變量和getInstance方法必須是static的。通過(guò)雙檢查機(jī)制可以滿足單例對(duì)象的構(gòu)造了,但是這種方法并不是完全正確的。主要問(wèn)題出在下面這句代碼上:
instance = new DCLSingleton();
這句話的執(zhí)行過(guò)程一般分為這么幾步:
- 在堆內(nèi)存中為即將實(shí)例化的對(duì)象分配內(nèi)存空間。
- 對(duì)象實(shí)例化操作
- 將內(nèi)存地址值賦給instance
這在JVM中可能出現(xiàn)指令重排的過(guò)程,因?yàn)榉峙溥^(guò)內(nèi)存后地址的值就確定了,jvm認(rèn)為步驟2和3前后調(diào)整對(duì)最終結(jié)果并不會(huì)產(chǎn)生影響,所以指令重排可能出現(xiàn)1->3->2順序的情況,當(dāng)instance被賦值了但對(duì)象實(shí)例化工作并沒(méi)有完成的時(shí)候,此時(shí)如果別的線程調(diào)用getInstance方法,由于instance!=null就會(huì)返回instance變量。這里返回的雖然是同一個(gè)對(duì)象,但是這個(gè)對(duì)象的狀態(tài)是不一致的(可能初始化還未完成)。所以** 這種方式獲得的對(duì)象并不是嚴(yán)格意義上的單例對(duì)象(同一狀態(tài)的同一個(gè)對(duì)象)。為了避免指令重排現(xiàn)象的發(fā)生,可以通過(guò)volatile來(lái)修飾instance變量。定義如下:
private static volatile DCLSingleton instance;
方法三:基于靜態(tài)成員變量的實(shí)現(xiàn)方案
public class StaticSingleton {
private StaticSingleton(){ System.out.println("instance is created!"); }
/** * instance實(shí)例化是在類加載階段進(jìn)行的,天生線程友好,保證對(duì)象唯一性。 */
private static StaticSingleton instance = new StaticSingleton();
public static StaticSingleton getInstance(){
return instance;
}
}
靜態(tài)成員變量的實(shí)現(xiàn)方案主要利用了靜態(tài)成員變量的初始化時(shí)機(jī)是在類的加載階段這一特性。我們都知道類的加載階段包括(加載-連接-初始化),這里重點(diǎn)說(shuō)下初始化階段jvm做的工作,初始化階段會(huì)執(zhí)行所有類變量(靜態(tài)成員變量)的賦值動(dòng)作和靜態(tài)語(yǔ)句塊中的內(nèi)容。上面例子中也可以改用靜態(tài)語(yǔ)句塊實(shí)現(xiàn),如下:
private static StaticSingleton instance;
static{
instance = new StaticSingleton();
}
這種實(shí)現(xiàn)方案可以滿足單例模式的基本需求,但是如果我們某些情況調(diào)用了單例類的其他的靜態(tài)方法,則類的實(shí)例化操作同樣會(huì)被觸發(fā),如下所示:
public class StaticSingleton {
private StaticSingleton(){ System.out.println("instance is created!"); }
private static StaticSingleton instance = new StaticSingleton();
public static StaticSingleton getInstance(){ return instance; }
public static String otherMethod(){ return "msg"; }}
//outside code
public static void main(String args[]){
//call otherMethod
StaticSingleton.otherMethod();
}
}
輸出:instance is created!
我們希望獲得一種延遲加載機(jī)制,即我什么時(shí)候調(diào)用getInstance方法,單例對(duì)象的實(shí)例化才被觸發(fā)。此時(shí)可以使用靜態(tài)內(nèi)部類來(lái)實(shí)現(xiàn)LazyInit這一特性。
方法四:基于靜態(tài)內(nèi)部類實(shí)現(xiàn)
public class StaticSingleton {
private StaticHolderSingleton(){ System.out.println("instance is created!"); }
private static StaticSingleton instance ;
/** * 使用靜態(tài)內(nèi)部類實(shí)現(xiàn)單例 * */
private static class SingletonHolder{
private static StaticSingleton instance = new StaticSingleton();
}
public static StaticSingleton getInstance(){
return SingletonHolder.instance;
}
}
在這種實(shí)現(xiàn)方案中,如果調(diào)用getInstance之外的方法,instance變量并不會(huì)初始化。這就達(dá)到了lazy init的目的。
方法五:基于枚舉類型的實(shí)現(xiàn)
public class EnumSingleton{
/** * 枚舉類型實(shí)現(xiàn) */
enum SingletonHoler{
holder;
private SingletonDemo instance;
SingletonHoler(){ instance = new SingletonDemo(); }
public SingletonDemo getInstance(){ return instance; }
}
public static SingletonDemo getInstance(){
return SingletonHoler.holder.getInstance();
}
}
總結(jié):方法一使用synchronized加鎖,保證了getInstance()方法調(diào)用的串行化,從而保證了實(shí)例的唯一性。最簡(jiǎn)單,但高并發(fā)情況下由于鎖的競(jìng)爭(zhēng)效率較低。
方法二:通過(guò)雙檢查鎖機(jī)制,也保證了實(shí)例的唯一性。這里要注意的是用volatile修飾成員變量。
方法三和方法四原理一致,利用了靜態(tài)成員和靜態(tài)塊在類初始化的時(shí)候就執(zhí)行這一特性。保證程序運(yùn)行時(shí)獲取的對(duì)象唯一性。
方法五和方法三四類似,使用枚舉類型時(shí),構(gòu)造方法會(huì)被自動(dòng)調(diào)用,用這個(gè)特性實(shí)現(xiàn)單例模式。
以上方法都可以在正常情況下實(shí)現(xiàn)單例,但是一些極端情況下還是會(huì)出現(xiàn)問(wèn)題。具體情況如下:
通過(guò)反射調(diào)用私有構(gòu)造函數(shù)來(lái)實(shí)例化一個(gè)對(duì)象。
/** * 利用反射生成對(duì)象 */
@Test public void ReflectSingletionTest() throws Exception{
//通過(guò)getInstance方法獲取對(duì)象instance1
DCLSingleton instance1 = DCLSingleton.getInstance();
Class cls = Class.forName("edu.ouc.pattern.singleton.DCLSingleton");
Constructor c = cls.getDeclaredConstructor(); c.setAccessible(true);
//通過(guò)反射獲取對(duì)象
instance2 DCLSingleton instance2 = (DCLSingleton) c.newInstance();
Assert.assertEquals(instance1, instance2); }
結(jié)果:
java.lang.AssertionError: expected:edu.ouc.pattern.singleton.DCLSingleton@4eaa3152but was:edu.ouc.pattern.singleton.DCLSingleton@4586793e
結(jié)果顯示兩次獲取的對(duì)象并不是同一個(gè)對(duì)象,這顯然不是我們想要的。為了避免這一情況,可以強(qiáng)制構(gòu)造函數(shù)的調(diào)用次數(shù)僅為一次,否則拋出相應(yīng)的異常。實(shí)現(xiàn)代碼片段如下:
//新增isSingle變量,初始為false
private volatile static Boolean isSingle = false;
/** * 私有的構(gòu)造函數(shù) */
private DCLSingleton(){
synchronized(isSingle){
if(!isSingle){
isSingle = true;
}else{
try { throw new Exception("duplicate instance exception : " + DCLSingleton.class.getName());
} catch (Exception e) { e.printStackTrace(); }
}
}
}
此時(shí)再運(yùn)行ReflectSingletionTest()方法,結(jié)果如下:
java.lang.Exception: duplicate instance exception :
edu.ouc.pattern.singleton.DCLSingleton> at
edu.ouc.pattern.singleton.DCLSingleton.<init>(DCLSingleton.java:14)