單例模式是使用得最多的設計模式,模版代碼也很多。但是如果使用不當還是容易出問題。
DCL模式(雙重檢查鎖定模式)的正確使用方式
一般我們使用DCL方法來實現單例模式時都是這樣的模版代碼:
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;
}
實際上,上述方法在多線程的環境下,還是會有可能創建多個實例。為什么呢?
mSingleton = new Singleton()這行代碼虛擬機在執行的時候會有多個操作,大致包括:
- 為新的對象分配內存
- 調用Singleton的構造方法,初始化成員變量
- 將mSingleton這個引用指向新創建的Singleton對象的地址
在多線程環境下,每個線程的私有內存空間中都有mSingleton的副本。這導致可能存在下面的情況:
- 當在一個線程中初始化mSingleton后,主內存中的mSingleton變量的值可能并沒有及時更新;
- 主內存的mSingleton變量已經更新了,但在另一個線程中的mSingleton變量沒有即時從主內存中讀取最新的值
這樣的話就有可能創建多個實例,雖然這種幾率比較小。
那怎么解決這個問題呢?答案是使用volatile關鍵字
volatile關鍵字能夠保證可見性,被volatile修飾的變量,在一個線程中被改變時會立刻同步到主內存中,而另一個線程在操作這個變量時都會先從主內存更新這個變量的值。
更保險的單例模式實現
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;
}
使用單例模式,小心內存泄漏了喔~
單例模式的靜態特性導致它的對象的生命周期是和應用一樣的,如果不注意這一點就可能導致內存泄漏。下面看看常見的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的引用,如果當前MyActivity退出了,但應用還沒有退出,singleInstance一直持有MyActivity的引用,MyActivity就不能被回收了。
解決方法也很簡單,傳入ApplicationContext就可以了。
SingleInstance singleInstance = SingleInstance.getInstance(getApplicationContext());
- View的泄漏
如果單例模式的類中有跟View相關的屬性,就需要注意了。搞不好也會導致內存泄漏,原因和上面分析的原因一樣。
//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的引用會導致內存泄漏
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);
}
很多東西雖然簡單,還是有我們需要注意的地方。這就需要我們理解它們的特性了。比如上面用了弱引用來解決內存泄漏的問題,那我們就需要明白弱引用的特點,需要注意使用弱引用的變量可能為空的問題
被弱引用關聯的對象只能生存到下一次垃圾收集發生之前,當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象
今天你進步了嘛?歡迎關注我的微信公眾號,和我一起每天進步一點點!