設計模式中我們接觸的最多的可能要算單例模式了,只要我們想一個類只有一個實例存在,我們就會考慮使用單例模式,對于一個剛接觸編程不久的同學來說如何使用好單例可能還是有一定的困難的,今天就來告訴大家如何使用好單例這種設計模式。
其實單例模式可以分為5中,一種是懶漢式的,一種饑餓式,一種靜態內部類的形式,一種枚舉類的形式(推薦使用),雙重校驗鎖的形式。
對于一般的同學些單例,可能只是考慮把構造方法私有化,沒有考慮多線程的情況,一般會這樣寫:
~~~
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (null == singleton) {
singleton = new Singleton();
}
return singleton;
}
}
~~~
這是懶漢式的寫法,不過這樣有一個問題就是沒有考慮到同步,可能你會說,同步還不簡單嘛,直接在getInstance方法中加synchronized,可是這樣寫有一個性能的問題,因為getInstance方法我們經常會調用,而如果這個方法又是同步的,顯然會有性能問題,那么如何解決這個問題呢?可能你想到了只在創建實例的時候加synchronized關鍵字,于是你會這么寫:
~~~
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
synchronized (Singleton.class) {
if (null == singleton) {
singleton = new Singleton();
}
return singleton;
}
}
}
~~~
但是這樣寫依然存在一個性能問題,就是你每次判斷它是否為空的時候都要執行同步代碼塊,如果解決這個問題呢?這就形成了我們的雙重校驗鎖的雛形,我們判斷兩次是否為空,第二次不為空的時候再去創建實例,這樣就解決了性能的問題:
~~~
public class Singleton {
private static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (null == singleton) {
synchronized (Singleton.class) {
if (null == singleton)
singleton = new Singleton();
}
}
return singleton;
}
}
~~~
但是這樣寫會存在一個多處理器創建的時候的問題,我們知道一個類被創建的時候是需要一定時間的,cup1判斷變量為空,然后他去實例化這個類,可是在實例的過程中還沒有完成的時候,cup2需要使用這個類,它去判斷的時候發現這個類已經被cup1實例化了,于是它就直接使用,但是cup1實例化還沒有完成,這個類還是一個半成品,這個時候cup2使用必定會出現一些莫名其妙的問題,如何解決這個問題呢?好在有一個關鍵字volatile,這個就是用來解決多處理器變量共享的問題,于是一個完美的雙重校驗鎖應該這樣寫:
~~~
public class Singleton {
private volatile static Singleton singleton;
private Singleton() {
}
public static Singleton getInstance() {
if (null == singleton) {
synchronized (Singleton.class) {
if (null == singleton)
singleton = new Singleton();
}
}
return singleton;
}
}
~~~
上面說的懶漢式的寫法,饑漢式因為本身就不存在多線程的問題,所以不必要考慮多線程,一個標準的寫法如下:
~~~
public class Singleton {
private static Singleton singleton=new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return singleton;
}
}
~~~
我們知道static修飾的全局變量在類加載的時候就被初始化了,所以不存在多線程的問題,但是這樣也存在一個問題,就是有時候我們在類被加載的時候不想實例化這個變量,我們需要在getInstance的時候再去創建變量,可見餓漢式是做不到的,這也就是餓漢式的缺點。為了解決這個問題,就出現了靜態內部類的形式,這種形式很輕松的解決了這個問題,寫法如下:
~~~
public class Singleton {
private Singleton() {
}
public static Singleton getInstance() {
return SingletonHolder.SINGLETON;
}
private static class SingletonHolder{
private static Singleton SINGLETON=new Singleton();
}
}
~~~
這樣就可以保證只有在調用getInstance的時候再去加載內部類,加載內部類的時候再去實例化變量,而這樣也很輕松的解決了多線程的問題。
最后一種很不常用但是去推薦使用的形式是枚舉的形式,寫法如下:
~~~
public enum Singleton {
INSTANCE;
private Singleton() {
}
public String[] getName() {
final String[] names = new String[3];
names[0] = "青椒";
names[1] = "大白菜";
names[2] = "牛蛙";
return names;
}
}
~~~
然后在MainActivity里使用的時候我們可以這樣用:
~~~
Log.e("測試你喜歡吃的食物:",Singleton.INSTANCE.getName()[0]);
~~~
**總結:**
使用單例模式還是存在一定的問題的,比如我們雖然使用private保證了類不能被new出來,可是在java的反射機制中,private是沒有作用的,這樣就不能保證單例實例的唯一性,再比如在餓漢式中,雖然變量是使用類加載器來實例化的能保證為唯一性,可是如果存在多個類加載器就無法保證唯一性,所以今后能不使用單例還是避免使用它吧,如果真的沒辦法必須使用,建議使用靜態內部類、雙重鎖、枚舉這3種的一種,優先考慮使用枚舉。ok,今天的單例就說到這里了,我們慢慢的把26中設計模式都給大家過一遍,期待我的更新吧!!