單例模式是使用得最多的設(shè)計(jì)模式俗冻,模版代碼也很多梨水。但是如果使用不當(dāng)還是容易出問題乏矾。
DCL模式(雙重檢查鎖定模式)的正確使用方式
一般我們使用DCL方法來實(shí)現(xiàn)單例模式時(shí)都是這樣的模版代碼:
private static Singleton mSingleton = null;
private Singleton () {}
public static Singleton getInstance() {
if (mSingleton == null) {
synchronized (Singleton.class) {
if (mSingleton == null) {
mSingleton = new Singleton();
}
}
}
return mSingleton;
}
實(shí)際上屋剑,上述方法在多線程的環(huán)境下润匙,還是會(huì)有可能創(chuàng)建多個(gè)實(shí)例。為什么呢唉匾?
mSingleton = new Singleton()這行代碼虛擬機(jī)在執(zhí)行的時(shí)候會(huì)有多個(gè)操作孕讳,大致包括:
- 為新的對象分配內(nèi)存
- 調(diào)用Singleton的構(gòu)造方法,初始化成員變量
- 將mSingleton這個(gè)引用指向新創(chuàng)建的Singleton對象的地址
在多線程環(huán)境下肄鸽,每個(gè)線程的私有內(nèi)存空間中都有mSingleton的副本卫病。這導(dǎo)致可能存在下面的情況:
- 當(dāng)在一個(gè)線程中初始化mSingleton后,主內(nèi)存中的mSingleton變量的值可能并沒有及時(shí)更新典徘;
- 主內(nèi)存的mSingleton變量已經(jīng)更新了蟀苛,但在另一個(gè)線程中的mSingleton變量沒有即時(shí)從主內(nèi)存中讀取最新的值
這樣的話就有可能創(chuàng)建多個(gè)實(shí)例,雖然這種幾率比較小逮诲。
那怎么解決這個(gè)問題呢帜平?答案是使用volatile關(guān)鍵字
volatile關(guān)鍵字能夠保證可見性,被volatile修飾的變量梅鹦,在一個(gè)線程中被改變時(shí)會(huì)立刻同步到主內(nèi)存中裆甩,而另一個(gè)線程在操作這個(gè)變量時(shí)都會(huì)先從主內(nèi)存更新這個(gè)變量的值。
更保險(xiǎn)的單例模式實(shí)現(xiàn)
private volatile static Singleton mSingleton = null;
private Singleton () {}
public static Singleton getInstance() {
if (mSingleton == null) {
synchronized (Singleton.class) {
if (mSingleton == null) {
mSingleton = new Singleton();
}
}
}
return mSingleton;
}
使用單例模式齐唆,小心內(nèi)存泄漏了喔~
單例模式的靜態(tài)特性導(dǎo)致它的對象的生命周期是和應(yīng)用一樣的嗤栓,如果不注意這一點(diǎn)就可能導(dǎo)致內(nèi)存泄漏。下面看看常見的2種情況
- Context的泄漏
//SingleInstance.class
private volatile static SingleInstance mSingleInstance = null;
private SingleInstance (Context context) {}
public static SingleInstance getInstance(Context context) {
if (mSingleInstance == null) {
synchronized (SingleInstance.class) {
if (mSingleInstance == null) {
mSingleInstance = new SingleInstance(context);
}
}
}
return mSingleInstance;
}
//MyActivity
public class MyActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//這樣就容易出問題了
SingleInstance singleInstance = SingleInstance.getInstance(this);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
如上面那樣直接傳入MyActivity的引用,如果當(dāng)前MyActivity退出了茉帅,但應(yīng)用還沒有退出叨叙,singleInstance一直持有MyActivity的引用,MyActivity就不能被回收了堪澎。
解決方法也很簡單擂错,傳入ApplicationContext就可以了。
SingleInstance singleInstance = SingleInstance.getInstance(getApplicationContext());
- View的泄漏
如果單例模式的類中有跟View相關(guān)的屬性樱蛤,就需要注意了钮呀。搞不好也會(huì)導(dǎo)致內(nèi)存泄漏,原因和上面分析的原因一樣昨凡。
//SingleInstance.class
private volatile static SingleInstance mSingleInstance = null;
private SingleInstance (Context context) {}
public static SingleInstance getInstance(Context context) {
if (mSingleInstance == null) {
synchronized (SingleInstance.class) {
if (mSingleInstance == null) {
mSingleInstance = new SingleInstance(context);
}
}
}
return mSingleInstance;
}
//單例模式中這樣持有View的引用會(huì)導(dǎo)致內(nèi)存泄漏
private View myView = null;
public void setMyView(View myView) {
this.myView = myView;
}
解決方案是采用弱引用
private volatile static SingleInstance mSingleInstance = null;
private SingleInstance (Context context) {}
public static SingleInstance getInstance(Context context) {
if (mSingleInstance == null) {
synchronized (SingleInstance.class) {
if (mSingleInstance == null) {
mSingleInstance = new SingleInstance(context);
}
}
}
return mSingleInstance;
}
// private View myView = null;
// public void setMyView(View myView) {
// this.myView = myView;
// }
//用弱引用
private WeakReference<View> myView = null;
public void setMyView(View myView) {
this.myView = new WeakReference<View>(myView);
}
很多東西雖然簡單爽醋,還是有我們需要注意的地方。這就需要我們理解它們的特性了土匀。比如上面用了弱引用來解決內(nèi)存泄漏的問題子房,那我們就需要明白弱引用的特點(diǎn),需要注意使用弱引用的變量可能為空的問題
被弱引用關(guān)聯(lián)的對象只能生存到下一次垃圾收集發(fā)生之前就轧,當(dāng)垃圾收集器工作時(shí),無論當(dāng)前內(nèi)存是否足夠田度,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對象
今天你進(jìn)步了嘛妒御?歡迎關(guān)注我的微信公眾號,和我一起每天進(jìn)步一點(diǎn)點(diǎn)镇饺!