單例模式是在一個應(yīng)用(Application)中某個類(Class)有且僅有一個實例(Instance)存在。
相當(dāng)于一個全局對象弓熏,方便對整個系統(tǒng)的行為進行協(xié)調(diào)恋谭,比如服務(wù)器的配置信息由一個單例對象保存。
單例模式又可細分為 懶漢方式(lazy-initialization)和餓漢方式挽鞠,懶漢方式是只有需要的時候才會去創(chuàng)建疚颊,餓漢方式則是在Application初始化或者.class類文件加載階段直接創(chuàng)建。
餓漢模式(不存在線程不安全的問題):
public class Singleton {
private final static Singleton INSTANCE = new Singleton();
// Private constructor suppresses
private Singleton() {}
// default public constructor
public final static Singleton getInstance() {
return INSTANCE;
}
}
final static
的INSTANCE
只有在Singleton的成員變量或者函數(shù)(非final static literal成員變量)第一次被使用的時候才會觸發(fā)實例化信认。在Singleton
類加載到JVM的時候INSTANCE
被放到JVM方法區(qū)常量池中材义,其值new Singleton()
則只是存了Singleton
類以及constructor方法的符號引用,實例化被JVM推遲嫁赏。
一般的懶漢模式(非線程安全):
public class Singleton {
private static Singleton INSTANCE = null;
private Singleton() {}
public static Singleton getInstance() {
if(INSTANCE == null){ INSTANCE = new Singleton(); }
}
return INSTANCE;
}
試想在單核模式下當(dāng)兩個線程t1和t2一同訪問getInstance()
函數(shù)母截,t1先檢查INSTANCE
是否是null
之后被掛起,t2檢查INSTANCE
是null
之后創(chuàng)建Singleton
的instance并掛起橄教,t1被喚醒再次創(chuàng)建Singleton
的instance清寇,造成了數(shù)據(jù)的覆蓋。
一般的懶漢模式(線程安全):
public class Singleton {
private static Singleton INSTANCE = null;
private Singleton() {}
public static synchronized Singleton getInstance() {
if(INSTANCE == null){ INSTANCE = new Singleton(); }
}
return INSTANCE;
}
直接給getInstance()
Class級別函數(shù)加互斥鎖护蝶,每個訪問該函數(shù)的線程都得先拿到該Class的monitor鎖华烟。但是這樣過多的加鎖解鎖消耗資源不夠efficiency。
Double Check的懶漢模式(線程安全):
public class Singleton {
private static volatile Singleton INSTANCE = null;
// Private constructor suppresses
private Singleton() {}
//thread safe and performance promote
public static Singleton getInstance() {
if(INSTANCE == null){
synchronized(Singleton.class){
if(INSTANCE == null){
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
相比前一個實現(xiàn)持灰,每次getInstance
都先檢查是否是null
盔夜,如果不是null
則不執(zhí)行加鎖解鎖的操作,所以更高效堤魁。
volatile
關(guān)鍵字的作用是使變量內(nèi)存可見(visible to memory)喂链,防止代碼重排序(確保happens-before原則)。
synchronized
關(guān)鍵字則會為Singleton.class
加monitor鎖妥泉,每個調(diào)用該段代碼的Thread必須先拿到Singleton.class
的monitor鎖才能執(zhí)行該段代碼椭微,而同一時間只能有一個Thread持有該monitor鎖。
靜態(tài)內(nèi)部類的實現(xiàn):
public final class Singleton{
public static class SingletonHandler{
private final static Singleton INSTANCE=new Singleton();
}
public final static Singleton getInstanc(){
return SingletonHandler.INSTANCE;
}
}
內(nèi)部類StingletonHandler
在編譯之后會生成一個單獨的.class
文件:Singleton$SingletonHandler.class
盲链。Singleton.class
被加載方法區(qū)之后保留的只是SingletonHandler
和SingletonHandler.INSTANCE
的qualified name(全限定名的符號引用symbolic reference)蝇率,只有在調(diào)用getInstance
方法時JVM才會通過classloader將Singleton$SingletonHandler.class
加載到方法區(qū)迟杂,在heap區(qū)創(chuàng)建Singleton
的實例,并將符號引用(symbolic reference)轉(zhuǎn)換為直接引用(direct reference)本慕。
以上加載過程都是線程安全的排拷,所以該實現(xiàn)高效&自動線程安全。
使用場景:當(dāng)Singleton這個類有提供其他很多static
的函數(shù)的話锅尘,通過SingletonHandler
可以實現(xiàn)懶加載(僅在需要用到這個instance的時候才去加載內(nèi)部類并創(chuàng)建Singleton
的instance)监氢。
Enum實現(xiàn):
public Enum Singleton{
INSTANCE;
//functions to add
}
Decompile之后的代碼是:
public final class Singleton extends Enum
{
private Singleton(String s, int i)
{
super(s, i);
}
public static Singleton[] values()
{
Singleton asingleton[];
int i;
Singleton asingleton1[];
System.arraycopy(asingleton = ENUM$VALUES, 0, asingleton1 = new Singleton[i = asingleton.length], 0, i);
return asingleton1;
}
public static Singleton valueOf(String s)
{
return (Singleton)Enum.valueOf(structure/proxy/Singleton, s);
}
public static final Singleton INSTANCE;
private static final Singleton ENUM$VALUES[];
static
{
INSTANCE = new Singleton("INSTANCE", 0);
ENUM$VALUES = (new Singleton[] {
INSTANCE
});
}
}
和餓漢模式很像,相比而言優(yōu)點是寫法更簡單藤违,而且自動提供了Serialisable序列化(其他單例實現(xiàn)的序列化則需手動implements Serialisable接口實現(xiàn)序列化)忙菠。
關(guān)于JVM類加載流程在這里先簡單寫一下:
- loading -> 找.class文件(TYPE)并加載入JVM
- linking -> 分三部分
2.1. verification -> 檢查引入TYPE文件正確性
2.2. preparation -> 給class變量分配內(nèi)存(在方法區(qū))并賦值default value:(boolean:false, int:0, reference:null)
2.3. resolution -> 將symbolic reference轉(zhuǎn)為direct reference (通常延后觸發(fā)) - initialization -> 觸發(fā)代碼提供的賦值語句(通常延后觸發(fā))
歡迎批評指正,謝謝纺弊!
Reference
https://zh.wikipedia.org/wiki/單例模式
https://stackoverflow.com/questions/70689/what-is-an-efficient-way-to-implement-a-singleton-pattern-in-java
https://stackoverflow.com/questions/16771373/singleton-via-enum-way-is-lazy-initialized