(其實設計模式應該從屬于java,但是會專門針對android做相應的解釋,所以就取名為android設計模式~)
一.單例模式的介紹
單例模式是應用最廣的模式之一,在應用這個模式的時候,單例對象的類必須保證只有一個實例存在。在android中的應用場景例如整個app只有一個application對象,只有一個ImageLoader對象等。
二.單例模式下的各種實現方式
1.餓漢模式
public class Singleton {
private Singleton() {} //在該類初始化的時候就會自行實例化
private static final Singleton single = new Singleton();
public static Singleton getInstance() {
return single;
}
}
2.懶漢模式
public class Singleton {
private Singleton(){}
private final static Singleton mInstance; //沒有care線程安全的問題
public static Singleton getInstance() {
if(mInstance == null){
mInstance = new Singleton();
}
return mInstance;
}
}
Tips:不論是餓漢模式還是懶漢模式,可能在你的app中都占有一席之地。那么我們先來對比下兩者的區別,首先餓漢模式會在該類初始化的時候就自動實例化,而懶漢模式則會在對應調用getInstance方法時才會對應的實例化,實現了實例的延時加載。設想如果該實例在app中不一定被使用到,那么使用懶漢模式就可以節省內存。但是懶漢模式會在第一次獲取實例時較為耗時,餓漢模式由于在初始化類時就進行了實例化,第一次獲取實例就不會耗時。
以上是針對餓漢模式和懶漢模式之間的區別做的分析,接下來我們來關注之前代碼中對于懶漢模式線程不安全的問題。分別提供以下幾種解決方案來進行對比:
2.1.在getInstance方法上加同步鎖
public class Singleton {
private Singleton(){}
private final static Singleton mInstance; //加上同步鎖
public static synchronized Singleton getInstance() {
if(mInstance == null){
mInstance = new Singleton();
}
return mInstance;
}
}
這種方法雖然解決了線程安全的問題,但是單例模式一般都是應用在一些會被頻繁調用的場景上的,如果在每次獲取實例的時候都需要去進行線程同步,那會增加不小的開銷,會使單例的獲取變的緩慢,這樣就得不償失了。那么我們繼續改進,看下面的方法:
2.2.Double Check Lock(DCL)實現單例
public class Singleton {
private Singleton(){}
private final static Singleton mInstance;
/*雙重鎖定:只在第一次初始化的時候加上同步鎖*/
public static Singleton getInstance() {
if(mInstance == null){
synchronized(Singleton.class){
if(mInstance == null){
mInstance = new Singleton();
}
}
}
return mInstance;
}
}```
這種雙重鎖定的方式,避免了每次獲取實例時不必要的同步操作,只在第一次獲取實例的時候才進行同步,將開銷減到了最小,并且保證了線程安全。但是,真的是線程安全了么?問題其實出在mInstance = new Singleton();這句代碼,雖然它只是一句代碼,但是實際上它不是一個原子操作,這句代碼最終會被編譯成多條匯編指令,它大致做了3件事情:
(1)給Singleton的實例分配內存;
(2)調用Singleton()的構造函數,初始化成員字段;
(3)將mInstance對象指向分配的內存空間(此時mInstance就不是null了)。
由于Java編譯器允許處理器亂序執行,以及JDK1.5之前JMM(Java Memory Model,即Java內存模型)中Cache、寄存器到內存回寫順序的規定,上面的第二和第三的順序是無法保證的。也就是說,執行順序可能是1-2-3也可能是1-3-2。如果是后者,并且在3執行完畢、2未執行之前,被切換到另一個線程上,就會出問題。但是在你的app沒有太多的高并發存在時,這種模式已經可以完全滿足大多數開發者的需求。那么一定還有更好的:
#### 2.3.靜態內部類單例模式
```java
public class Singleton {
private Singleton(){}
private final static Singleton mInstance;
public static Singleton getInstance() {
return SingletonHolder.mInstance;
}
private static class SingletonHolder {
private final static Singleton mInstance = new Singleton();
}
}
當第一次加載Singleton類的時候并不會初始化mInstance,只有在第一次調用getInstance方法時才會導致mInstance被初始化。因此,第一次調用getInstance方法會導致虛擬機加載SingletonHolder類,這種方式不僅能夠確保線程安全,也能夠保證單例對象的唯一性,同時也延遲了單例的實例化,所以這是推薦使用的單例模式實現方式。Tip:java中的枚舉其實也是單例的一種實現方式
三.結論
之前在對單例的了解并沒有特別的系統,這次梳理了下,發現其實自己的工程中還是有很多不考慮線程安全的單例實現的,雖然在沒有并發的情況下可能沒有太大的影響,但是程序是需要有超前意識的,推薦大家也使用2.3的單例實現方式。