目的:但願對象只建立一個實例,而且提供一個全局的訪問點。java
原文:http://www.iteye.com/topic/575052android
Singleton模式能夠是很簡單的,它的所有隻須要一個類就能夠完成(看看這章可憐的UML圖)。可是若是在「對象建立的次數以及什麼時候被建立」這兩點上較真起來,Singleton模式能夠至關的複雜,比頭五種模式加起來還複雜,譬如涉及到DCL雙鎖檢測(double checked locking)的討論、涉及到多個類加載器(ClassLoader)協同時、涉及到跨JVM(集羣、遠程EJB等)時、涉及到單例對象被銷燬後重建等。數據庫
目的:安全
但願對象只建立一個實例,而且提供一個全局的訪問點。服務器
圖6.1 單例模式的UML圖多線程
結構是簡單的,可是卻存在一下狀況;併發
1.每次從getInstance()都能返回一個且惟一的一個對象。ide
2.資源共享狀況下,getInstance()必須適應多線程併發訪問。函數
3.提升訪問性能。性能
4.懶加載(Lazy Load),在須要的時候才被構造。
這個寫法咱們把四點需求從上往下檢測,發現第2點的時候就出了問題,假設這樣的場景:兩個線程併發調用Singleton.getInstance(),假設線程一先判斷完instance是否爲null,既代碼中的line 12進入到line 13的位置。剛剛判斷完畢後,JVM將CPU資源切換給線程二,因爲線程一還沒執行line 13,因此instance仍然是空的,所以線程二執行了new Signleton()操做。片刻以後,線程一被從新喚醒,它執行的仍然是new Signleton()操做。因此這種設計的單例模式不能知足第2點需求。
下面咱們繼續
比起單例A僅僅在方法中多了一個synchronized修飾符,如今能夠保證不會出線程問題了。可是這裏有個很大(至少耗時比例上很大)的性能問題。除了第一次調用時是執行了SingletonKerriganB的構造函數以外,之後的每一次調用都是直接返回instance對象。返回對象這個操做耗時是很小的,絕大部分的耗時都用在synchronized修飾符的同步準備上,所以從性能上說很不划算。
看起來這樣已經達到了咱們的要求,除了第一次建立對象以外,其餘的訪問在第一個if中就返回了,所以不會走到同步塊中。已經完美了嗎?
咱們來看看這個場景:假設線程一執行到instance = new SingletonKerriganD()這句,這裏看起來是一句話,但實際上它並非一個原子操做(原子操做的意思就是這條語句要麼就被執行完,要麼就沒有被執行過,不能出現執行了一半這種情形)。事實上高級語言裏面非原子操做有不少,咱們只要看看這句話被編譯後在JVM執行的對應彙編代碼就發現,這句話被編譯成8條彙編指令,大體作了3件事情:
1.給Kerrigan的實例分配內存。
2.初始化Kerrigan的構造器
3.將instance對象指向分配的內存空間(注意到這步instance就非null了)。
可是,因爲Java編譯器容許處理器亂序執行(out-of-order),以及JDK1.5以前JMM(Java Memory Medel)中Cache、寄存器到主內存回寫順序的規定,上面的第二點和第三點的順序是沒法保證的,也就是說,執行順序多是1-2-3也多是1-3-2,若是是後者,而且在3執行完畢、2未執行以前,被切換到線程二上,這時候instance由於已經在線程一內執行過了第三點,instance已是非空了,因此線程二直接拿走instance,而後使用,而後瓜熟蒂落地報錯,並且這種難以跟蹤難以重現的錯誤估計調試上一星期都未必能找得出來,真是一茶几的杯具啊。
DCL的寫法來實現單例是不少技術書、教科書(包括基於JDK1.4之前版本的書籍)上推薦的寫法,其實是不徹底正確的。的確在一些語言(譬如C語言)上DCL是可行的,取決因而否能保證二、3步的順序。在JDK1.5以後,官方已經注意到這種問題,所以調整了JMM、具體化了volatile關鍵字,所以若是JDK是1.5或以後的版本,只須要將instance的定義改爲「private volatile static SingletonKerriganD instance = null;」就能夠保證每次都去instance都從主內存讀取,就可使用DCL的寫法來完成單例模式。固然volatile或多或少也會影響到性能,最重要的是咱們還要考慮JDK1.42以及以前的版本,因此本文中單例模式寫法的改進還在繼續。
代碼倒愈來愈複雜了,如今先來個返璞歸真,根據JLS(Java Language Specification)中的規定,一個類在一個ClassLoader中只會被初始化一次,這點是JVM自己保證的,那就把初始化實例的事情扔給JVM好了.
這種寫法不會出現併發問題,可是它是餓漢式的,在ClassLoader加載類後Kerrigan的實例就會第一時間被建立,餓漢式的建立方式在一些場景中將沒法使用:譬如實例的建立是依賴參數或者配置文件的,在getInstance()以前必須調用某個方法設置參數給它,那樣這種單例寫法就沒法使用了。
這種寫法仍然使用JVM自己機制保證了線程安全問題;因爲SingletonHolder是私有的,除了getInstance()以外沒有辦法訪問它,所以它是懶漢式的;同時讀取實例的時候不會進行同步,沒有性能缺陷;也不依賴JDK版本。
固然,用戶以其它方式構造單例的對象,若是設計者不但願這樣的狀況發生,則須要作規避措施。其它途徑建立單例實例的方式有:
1.直接new單例對象
2.經過反射構造單例對象
3.經過序列化構造單例對象。
對於第一種狀況,通常咱們會加入一個private或者protected的構造函數,這樣系統就不會自動添加那個public的構造函數了,所以只能調用裏面的static方法,沒法經過new建立對象。
對於第二種狀況,反射時可使用setAccessible方法來突破private的限制,咱們須要作到第一點工做的同時,還須要在在 ReflectPermission("suppressAccessChecks") 權限下使用安全管理器(SecurityManager)的checkPermission方法來限制這種突破。通常來講,不會真的去作這些事情,都是經過應用服務器進行後臺配置實現。
對於第三種狀況,若是單例對象有必要實現Serializable接口(不多出現),則應當同時實現readResolve()方法來保證反序列化的時候獲得原來的對象。
App路徑:packages/providers/CalendarProvider
文件:packages/providers/CalendarProvider/src/com/android/provider/calendar/CalendarDatabaseHelper.java
單例代碼:
private static CalendarDatabaseHelper sSingleton = null;
public static synchronized CalendarDatabaseHelper getInstance(Context context) {
if (sSingleton == null) {
sSingleton = new CalendarDatabaseHelper(context);
}
return sSingleton;
}
能夠看出,這是用到了2中的單例模式B.
文件:libcore/luni/src/main/java/com/ibm/icu4jni/text/Callator.java
libcore/luni/src/main/java/com/ibm/icu4jni/text/RuleBasedCallator.java
單例代碼:
public static Collator getInstance(Locale locale) {
return new RuleBasedCollator(locale);
}
RuleBasedCollator(Locale locale) {
m_collator_ = NativeCollation.openCollator(locale.toString());
}
static native int openCollator(String locale);
這就是上面給出的單例模式E,可帶參數的單例模式
文件:frameworks/base/core/java/android/text/Editable.java
private static Editable.Factory sInstance = new Editable.Factory();
/**
* Returns the standard Editable Factory.
*/
public static Editable.Factory getInstance() {
return sInstance;
}
可見這是單例模式D是實例應用
文件:frameworks/base/core/java/android/view/accessibility/AccessibilityManager.java
public static AccessibilityManager getInstance(Context context) {
synchronized (sInstanceSync) {
if (sInstance == null) {
sInstance = new AccessibilityManager(context);
}
}
return sInstance;
}
這是單例模式C的應用。
android使用單例模式的地方不少,特別是數據庫建立時,就會使用到單例模式。因每種單例模式試用場景不同,因此android在不一樣地方使用了不一樣的單例模式實現方式。