前段時間公司一些同事在討論單例模式(我是最渣的一個,都插不上嘴 T__T ),這個模式使用的頻率很高,也多是不少人最熟悉的設計模式,固然單例模式也算是最簡單的設計模式之一吧,簡單歸簡單,可是在實際使用的時候也會有一些坑。
PS:對技術感興趣的同鞋加羣544645972一塊兒交流。html
確保某一個類只有一個實例,並且自行實例化並向整個系統提供這個實例。
單例模式的使用很普遍,好比:線程池(threadpool)、緩存(cache)、對話框、處理偏好設置、和註冊表(registry)的對象、日誌對象,充當打印機、顯卡等設備的驅動程序的對象等,這些類的對象只能有一個實例,若是製造出多個實例,就會致使不少問題的產生,程序的行爲異常,資源使用過量,或者不一致的結果等,因此單例模式最主要的特色:android
類圖很簡單,Singleton 類有一個 static 的 instance對象,類型爲 Singleton ,構造函數爲 private,提供一個 getInstance() 的靜態函數,返回剛纔的 instance 對象,在該函數中進行初始化操做。git
單例模式的寫法不少,總結一下:程序員
延遲初始化,通常不少人稱爲懶漢法,寫法一目瞭然,在須要使用的時候去調用getInstance()函數去獲取Singleton的惟一靜態對象,若是爲空,就會去作一個額外的初始化操做。github
public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if(instance == null)
instance = new Singleton();
return instance;
}
}複製代碼
須要注意的是這種寫法在多線程操做中是不安全的,後果是可能會產生多個Singleton對象,好比兩個線程同時執行getInstance()函數時,而後同時執行到 new 操做時,最後頗有可能會建立兩個不一樣的對象。設計模式
須要作到線程安全,就須要確保任意時刻只能有且僅有一個線程可以執行new Singleton對象的操做,因此能夠在getInstance()函數上加上 synchronized 關鍵字,相似於:緩存
public static synchronized Singleton getInstance() {
if(singleton == null)
instance = new Singleton();
return instance;
}複製代碼
可是套用《Head First》上的一句話,對於絕大部分不須要同步的狀況來講,synchronized 會讓函數執行效率糟糕一百倍以上(Since synchronizing a method could in some extreme cases decrease performance by a factor of 100 or higher),因此就有了double-checked(雙重檢測)的方法:安全
public class Singleton {
private volatile static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if (instance == null){
synchronized (Singleton.class){
if (instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}複製代碼
咱們假設兩個線程A,B同時執行到了getInstance()這個方法,第一個if判斷,兩個線程同時爲true,進入if語句,裏面有個 synchronized 同步,因此以後有且僅有一個線程A會執行到 synchronized 語句內部,接着再次判斷instance是否爲空,爲空就去new Singleton對象而且賦值給instance,A線程退出 synchronized 語句,交出同步鎖,B線程進入 synchronized 語句內部,if判斷instance是否爲空,防止建立不一樣的instance對象,這也是第二個if判斷的做用,B線程發現不爲空,因此直接退出,因此最終A和B線程能夠獲取到同一個Singleton對象,以後的線程調用getInstance()函數,都會由於Instance不爲空而直接返回,不會受到 synchronized 的性能影響。多線程
double-checked方法用到了volatile關鍵字,volatile關鍵字的做用須要仔細介紹一下,在C/C++中,volatile關鍵字的做用和java中是不同的,總結一下:
上面有一個細節,java 5版本以後volatile的讀與寫才創建了一個happens-before的關係,以前的版本會出現一個問題:Why is volatile used in this example of double checked locking,這個答案寫的很清楚了,線程 A 在徹底構造完 instance 對象以前就會給 instance 分配內存,線程B在看到 instance 已經分配了內存不爲空就回去使用它,因此這就形成了B線程使用了部分初始化的 instance 對象,最後就會出問題了。Double-checked locking裏面有一句話
As of J2SE 5.0, this problem has been fixed. The volatile keyword now ensures that
multiple threads handle the singleton instance correctly. This new idiom is
described in [2] and [3].複製代碼
因此對於 android 來講,使用 volatile關鍵字是一點問題都沒有的了。
參考文章:
Volatile變量
C/C++ Volatile關鍵詞深度剖析
Java中volatile的做用以及用法
「餓漢法」就是在使用該變量以前就將該變量進行初始化,這固然也就是線程安全的了,寫法也很簡單:
private static Singleton instance = new Singleton();
private Singleton(){
name = "eager initialization thread-safety 1";
}
public static Singleton getInstance(){
return instance;
}複製代碼
或者
private static Singleton instance = null;
private Singleton(){
name = "eager initialization thread-safety 2";
}
static {
instance = new Singleton();
}
public Singleton getInstance(){
return instance;
}複製代碼
代碼都很簡單,一個是直接進行初始化,另外一個是使用靜態塊進行初始化,目的都是一個:在該類進行加載的時候就會初始化該對象,而無論是否須要該對象。這麼寫的好處是編寫簡單,並且是線程安全的,可是這時候初始化instance顯然沒有達到lazy loading的效果。
因爲在java中,靜態內部類是在使用中初始化的,因此能夠利用這個天生的延遲加載特性,去實現一個簡單,延遲加載,線程安全的單例模式:
private static class SingletonHolder{
private static final Singleton instance = new Singleton();
}
private Singleton(){
name = "static inner class thread-safety";
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}複製代碼
定義一個 SingletonHolder 的靜態內部類,在該類中定義一個外部類 Singleton 的靜態對象,而且直接初始化,在外部類 Singleton 的 getInstance() 方法中直接返回該對象。因爲靜態內部類的使用是延遲加載機制,因此只有當線程調用到 getInstance() 方法時纔會去加載 SingletonHolder 類,加載這個類的時候又會去初始化 instance 變量,因此這個就實現了延遲加載機制,同時也只會初始化這一次,因此也是線程安全的,寫法也很簡單。
上面提到的全部實現方式都有兩個共同的缺點:
JDK1.5 以後加入 enum 特性,可使用 enum 來實現單例模式:
enum SingleEnum{
INSTANCE("enum singleton thread-safety");
private String name;
SingleEnum(String name){
this.name = name;
}
public String getName(){
return name;
}
}複製代碼
使用枚舉除了線程安全和防止反射強行調用構造器以外,還提供了自動序列化機制,防止反序列化的時候建立新的對象。所以,Effective Java推薦儘量地使用枚舉來實現單例。可是很不幸的是 android 中並不推薦使用 enum ,主要是由於在 java 中枚舉都是繼承自 java.lang.Enum 類,首次調用時,這個類會調用初始化方法來準備每一個枚舉變量。每一個枚舉項都會被聲明成一個靜態變量,並被賦值。在實際使用時會有點問題,這是 google 的官方文檔介紹:
Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android
這篇博客也專門計算了 enum 的大小:胡凱-The price of ENUMs,因此枚舉寫法的缺點也就很明顯了。
登記式單例實際上維護了一組單例類的實例,將這些實例存放在一個Map(登記薄)中,對於已經登記過的實例,則從Map直接返回,對於沒有登記的,則先登記,而後返回。
//相似Spring裏面的方法,將類名註冊,下次從裏面直接獲取。 public class Singleton { private static Mapmap = new HashMap 複製代碼(); static{ Singleton single = new Singleton(); map.put(single.getClass().getName(), single); } //保護的默認構造子 protected Singleton(){} //靜態工廠方法,返還此類唯一的實例 public static Singleton getInstance(String name) { if(name == null) { name = Singleton.class.getName(); System.out.println("name == null"+"--->name="+name); } if(map.get(name) == null) { try { map.put(name, (Singleton) Class.forName(name).newInstance()); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } return map.get(name); } //一個示意性的商業方法 public String about() { return "Hello, I am RegSingleton."; } public static void main(String[] args) { Singleton single3 = Singleton.getInstance(null); System.out.println(single3.about()); } }
這種方式我極少見到,另外其實內部實現仍是用的餓漢式單例,由於其中的static方法塊,它的單例在類被裝載的時候就被實例化了。
綜上所述,平時在 android 中使用 double-checked 或者 SingletonHolder 都是能夠的,畢竟 android 早就不使用 JDK5 以前的版本了。因爲 android 中的多進程機制,在不一樣進程中沒法建立同一個 instance 變量,就像 Application 類會初始化兩次同樣,這點須要注意。
可是無論採起何種方案,請時刻牢記單例的三大要點:
有些時候建立型模式是能夠重疊使用的,有一些抽象工廠模式和原型模式均可以使用的場景,這個時候使用任一設計模式都是合理的;在其餘狀況下,他們各自做爲彼此的補充:抽象工廠模式可能會使用一些原型類來克隆而且返回產品對象。
抽象工廠模式,建造者模式和原型模式都能使用單例模式來實現他們本身;抽象工廠模式常常也是經過工廠方法模式實現的,可是他們都可以使用原型模式來實現;
一般狀況下,設計模式剛開始會使用工廠方法模式(結構清晰,更容易定製化,子類的數量爆炸),若是設計者發現須要更多的靈活性時,就會慢慢地發展爲抽象工廠模式,原型模式或者建造者模式(結構更加複雜,使用靈活);
原型模式並不必定須要繼承,可是它確實須要一個初始化的操做,工廠方法模式必定須要繼承,可是不必定須要初始化操做;
使用裝飾者模式或者組合模式的狀況一般也可使用原型模式來得到益處;
單例模式中,只要將構造方法的訪問權限設置爲 private 型,就能夠實現單例。可是原型模式的 clone 方法直接無視構造方法的權限來生成新的對象,因此,單例模式與原型模式是衝突的,在使用時要特別注意。
www.tekbroaden.com/singleton-j…
hedengcheng.com/?p=725
www.cnblogs.com/hxsyl/archi…
www.blogjava.net/kenzhh/arch…
blog.csdn.net/jason0539/a…
sourcemaking.com/design_patt…
stackoverflow.com/questions/7…
stackoverflow.com/questions/1…
en.wikipedia.org/wiki/Single…
en.wikipedia.org/wiki/Double…
zh.wikipedia.org/wiki/Volati…
jeremymanson.blogspot.com/2008/11/wha…
www.jianshu.com/p/d8bf5d08a…
preshing.com/20130702/th…
blog.csdn.net/imzoer/arti…