單例模式在我們平時的開發中是比較常用的設計模式,那麼什麼是單例模式呢?單例模式的實現方法有那幾種方式呢?
我們首先了解下什麼是單例模式,單例模式就是確保一個類有且只有一個例項存在,而它的實現方式卻有很多種方法:餓漢式單例、懶漢式單例、DCL(double check lock)和靜態內部類單例模式,以及登記式單例和列舉單例,而這些實現方式都具有共同的特點: 1、宣告私有靜態變數; 2、私有(private)的建構函式; 3、提供靜態獲取例項的方法; 4、確保有且只有一個類的例項,並且該類物件在反序列化時不會重新構建物件。
那麼接下來我看下各種實現方式式如何實現的。
餓漢式單例
public class MyInstance {
private final static MyInstance mInstance = new MyInstance();
private MyInstance() {}
public static MyInstance getInstance(){
return mInstance;
}
}
複製程式碼
MyInstance 物件為靜態物件,宣告時候就已經初始化好了,而且不能通過new的形式構造物件,只能通過靜態方法getInstance,直接返回建立好的例項,這種方式實現單例模式是天生具有執行緒安全的。雖然餓漢式單例,獲取的時候能夠快速返回,節省了時間,但是卻佔用了空間,例項本身為static,會一直在記憶體中帶著。
懶漢式單例
public class MyInstance {
private static MyInstance mInstance = null;
private MyInstance() {}
public static synchronized MyInstance getInstance(){
if (mInstance == null) {
mInstance = new MyInstance();
}
return mInstance;
}
}
複製程式碼
使用懶漢式單例,每次獲取例項都需要synchronized 來同步,以確保例項的唯一性,每次呼叫getInstance方法都需要同步,造成了不必要的同步開銷,並且第一次載入時,需要及時例項化,不夠快。
DCL(double check lock)
public class MyInstance {
private static MyInstance mInstance = null;
private MyInstance() {}
public static MyInstance getInstance(){
if (mInstance == null) {
synchronized(MyInstance.class){
if (mInstance == null) {
mInstance = new MyInstance();
}
}
}
return mInstance;
}
}
複製程式碼
DCL方式:需要的時候才會去初始化,同時內部使用了synchronized來保證執行緒安全。使用兩次判空操作,第一次是如果物件已經初始化了,直接返回,不需要再做同步鎖了;第二次是為了在null的情況下建立例項。 使用DCL的優點就是資源利用率高,並且效率高,但是它也不是完美的,DCL第一次載入稍微慢了點,還有就是DCL失效的問題。關於DCL失效的問題,可以看《Android原始碼設計模式解析與實戰》裡面的第二章,關於單例模式的介紹。
靜態內部類單例模式
public class MyInstance {
private MyInstance() {}
public static MyInstance getInstance()
{
return SingleHodler.instance;
}
private static class SingleHodler{
private static final MyInstance instance=new MyInstance();
}
}
複製程式碼
第一次載入不會初始化instance,只要第一次呼叫getInstance方法的時候才會初始化getInstance,這種方式能夠確保執行緒安全和例項的唯一性,因為在多執行緒環境下,虛擬機器對一個類的初始化會做限制,同一時間只會允許一個執行緒去初始化一個類,這樣就從虛擬機器層面避免了大部分單例實現的問題。
登記式單例
public class MyInstance {
private static Map<String,MyInstance> map = new HashMap<String,MyInstance>();
private MyInstance() {
System.out.print("new");
}
public static MyInstance getInstance(String key){
if(key == null) {
key = MyInstance.class.getName();
}
if(map.get(key) == null){
try {
map.put(key, new MyInstance());
} catch (Exception e){
e.printStackTrace();
}
}
return map.get(key);
}
}
複製程式碼
將例項裝載加入集合map中,需要用的時候,直接從map中拿出來,一般的這種方式比較少用。
列舉單例
SdCardImpl:
public class SdCardImpl {
}
複製程式碼
列舉管理EnumManager :
public enum EnumManager {
SDCardManager(10)
{
@Override
public EnumManager getSingle() {
return SDCardManager;
}
}
,
HttpManager(1) {
@Override
public EnumManager getSingle() {
return null;
}
};
public SdCardImpl getSingleton()
{
return new SdCardImpl();
}
public abstract EnumManager getSingle();
private EnumManager(int type)
{
}
}
複製程式碼
呼叫:
EnumManager.SDCardManager.getSingleton();
複製程式碼
列舉單例是從java5引入的,具有的有點: 1、程式碼簡潔 2、執行緒安全 3、列舉單例可以自己處理序列化