核心原則:將構(gòu)造函數(shù)私有化衣撬,通過一個(gè)靜態(tài)內(nèi)部方法來(lái)獲取唯一實(shí)例.
單例模式的定義:確保某個(gè)類只有一個(gè)實(shí)例,避免產(chǎn)生多個(gè)對(duì)象來(lái)消耗過度資源。
下面介紹幾種常見實(shí)現(xiàn)單例模式的方法摔认。
餓漢模式
它是在聲明該靜態(tài)類時(shí)該對(duì)象已經(jīng)存在喧兄,并且初始化了.
public class Singleton{
private static Singleton mInstance = new Singleton();//注意這里的static无畔,因?yàn)橄旅嬲{(diào)用獲取到該實(shí)例的是靜態(tài)方法.
public static Singleton getInstance() {//靜態(tài)方法
return mInstance;
}
private Singleton() {//注意此處的構(gòu)造函數(shù)必須要用private來(lái)修飾
}
}
優(yōu)點(diǎn):寫的比較簡(jiǎn)單,線程安全.
缺點(diǎn):消耗點(diǎn)資源,因?yàn)榧词鼓悴挥迷搯卫庇ǎo態(tài)存儲(chǔ)空間中仍然會(huì)分配空間給該實(shí)例.
懶漢模式
它是懶加載的模式.用的時(shí)候加載檩互,不用的時(shí)候不占用空間
public class Singleton{
private static Singleton mInstance;
public static synchronized Singleton getInstance() {//注意這里必須要加synchronized,否則多線程調(diào)用就會(huì)有問題咨演,獲取到不唯一的實(shí)例.
if(mInstance == null) {
mInstance =new Singleton();
}
return mInstance;
}
}
優(yōu)點(diǎn):多線程安全闸昨,比餓漢模式好點(diǎn),它是需要的時(shí)候才實(shí)例化Singleton對(duì)象。
缺點(diǎn):調(diào)用getInstance的時(shí)候因?yàn)榧恿藄ynchronized鎖饵较,所以其他的線程調(diào)用的時(shí)候必須等待拍嵌,這樣造成了不必要的資源開銷。
DCL模式
Double Check Lock 模式循诉,用的時(shí)候初始化横辆,而且不需要getInstance方法進(jìn)行同步
public class Singleton{
private static volatile Singleton mInstance;//注意volatile這個(gè)關(guān)鍵字
public static Singleton getInstance() {
if(mInstance == null) {//第一次檢查
synchronized(Singleton.class){//鎖的是Singleton.class文件確保多線程安全
if(mInstance == null) {//第二次檢查
mInstance =new Singleton();
}
}
}
return mInstance;
}
}
volatile 這個(gè)關(guān)鍵字必須要用的原因
上面代碼中mInstance =new Singleton(); 其實(shí)并不是一個(gè)原子操作茄猫,編譯后大致做了下面3件事情
1)給Singleton分配內(nèi)存空間
2)構(gòu)造函數(shù)狈蚤,初始化成員變量
3)mInstance對(duì)象指向Singleton分配出來(lái)的存儲(chǔ)空間中
問題在于 Java編譯器允許處理器亂序執(zhí)行,所以上面的可能是1->3->2 比如兩個(gè)線程A,B分別調(diào)用了getInstance 靜態(tài)函數(shù)划纽,A執(zhí)行了 1->3->2的順序脆侮,當(dāng)執(zhí)行3的時(shí)候,B線程call getInstance 靜態(tài)函數(shù)獲取到的mInstance是非空的勇劣,直接返回靖避,這個(gè)時(shí)候B線程直接用這個(gè)實(shí)例就會(huì)有問題,因?yàn)榇藭r(shí)該實(shí)例還沒有初始化.這樣調(diào)用的話就會(huì)報(bào)錯(cuò).
基于上面的原因 JDK1.5之后引入了volatile這個(gè)關(guān)鍵字比默,使編譯器每次都是按1->2->3順序執(zhí)行幻捏,這樣就不會(huì)有多線程的問題,線程也安全命咐。
DCL的優(yōu)點(diǎn):資源的利用率高篡九,只有在使用的時(shí)候才初始化對(duì)應(yīng)的空間.而且高效的實(shí)現(xiàn)線程同步.
DCL的缺點(diǎn):使用了volatile關(guān)鍵字,第一次加載的速度上稍微慢了點(diǎn).
靜態(tài)內(nèi)部類
它是利用了Java虛擬機(jī)加載類的特性來(lái)解決了線程安全侈百,資源消耗等問題.
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.mInstance;
}
public static class SingletonHolder {
private static final Singleton mInstance = new Singleton()
}
}
這種方式, 通過JVM的類加載方式(虛擬機(jī)會(huì)保證一個(gè)類的初始化在多線程環(huán)境中被正確的加鎖瓮下、同步), 來(lái)保證了多線程并發(fā)訪問的正確性,由于靜態(tài)內(nèi)部類的加載特性--在使用時(shí)候才加載钝域,所以實(shí)現(xiàn)了懶加載的模式讽坏。
優(yōu)點(diǎn):
實(shí)現(xiàn)線程安全,實(shí)現(xiàn)懶加載
缺點(diǎn):
必須依賴Java虛擬機(jī)
枚舉單例
public enum Singleton{
INSTANCE;
}
優(yōu)點(diǎn):?jiǎn)卧孛杜e不僅能避免多線程同步問題例证,防止反序列化時(shí)重新創(chuàng)建新的對(duì)象
Android 用單例的例子
public final class InputMethodManager {
static InputMethodManager sInstance;
...
public static InputMethodManager getInstance() {
synchronized (InputMethodManager.class) {
if (sInstance == null) {
IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
sInstance = new InputMethodManager(service, Looper.getMainLooper());
}
return sInstance;
}
}
....
如InputMethodManager利用了懶加載的模式路呜,但是線程不安全.沒有加volatile關(guān)鍵字
public static synchronized CalendarDatabaseHelper getInstance(Contextcontext)
{
if (sSingleton == null)
{
sSingleton = newCalendarDatabaseHelper(context);
}
return sSingleton;
}
采用懶漢模式的CalendarDatabaseHelper類,對(duì)Calendar數(shù)據(jù)庫(kù)操作
結(jié)論:
1)實(shí)現(xiàn)單例模式分3步:構(gòu)造器私有化织咧,聲明私有靜態(tài)變量胀葱,提供靜態(tài)獲取實(shí)例的方法.
2)如果是單線程推薦懶加載模式,如果是多線程推薦靜態(tài)內(nèi)部類的方式實(shí)現(xiàn)單例
3)對(duì)數(shù)據(jù)庫(kù)操作笙蒙,對(duì)資源訪問抵屿,對(duì)IO操作等最好提供單例模式來(lái)處理