單例模式的幾種實現方式
package org.mlinge.s01; public class MySingleton { private static MySingleton instance = new MySingleton(); private MySingleton(){} public static MySingleton getInstance() { return instance; } }
package org.mlinge.s02; public class MySingleton { private static MySingleton instance = null; private MySingleton(){} public static MySingleton getInstance() { if(instance == null){ instance= new MySingleton(); } return instance; } }
public class MySingleton { private static MySingleton instance = null; private MySingleton(){} public synchronizedstatic MySingleton getInstance() { try { if(instance != null){//懶漢式 }else{ //建立例項之前可能會有一些準備性的耗時工作 Thread.sleep(300); instance = new MySingleton(); } } catch (InterruptedException e) { e.printStackTrace(); } return instance; } }
package org.mlinge.s03; public class MySingleton { private static MySingleton instance = null; private MySingleton(){} //public synchronized static MySingleton getInstance() { public static MySingleton getInstance() { try { synchronized (MySingleton.class) { if(instance != null){//懶漢式 }else{ //建立例項之前可能會有一些準備性的耗時工作 Thread.sleep(300); instance = new MySingleton(); } } } catch (InterruptedException e) { e.printStackTrace(); } return instance; } }
public class Singleton { private volatile static Singleton uniqueSingleton; private Singleton() { } public Singleton getInstance() { if (null == uniqueSingleton) { synchronized (Singleton.class) { if (null == uniqueSingleton) { uniqueSingleton = new Singleton(); } } } return uniqueSingleton; } }
// 需要在`uniqueSingleton`前加入關鍵字`volatile`。使用了volatile關鍵字後,重排序被禁止,所有的寫(write)操作都將發生在讀(read)操作之前
public class SingleTon{ private SingleTon(){} private static class SingleTonHoler{ private static SingleTon INSTANCE = new SingleTon(); } public static SingleTon getInstance(){ return SingleTonHoler.INSTANCE; } }
靜態內部類的優點是:外部類載入時並不需要立即載入內部類,內部類不被載入則不去初始化INSTANCE,故而不佔記憶體。即當SingleTon第一次被載入時,並不需要去載入 SingleTonHoler,只有當getInstance()方法第一次被呼叫時,才會去初始化INSTANCE,第一次呼叫getInstance()方法會導致虛擬機器載入SingleTonHoler類,這種方法不僅能確保執行緒安全,也能保證單例的唯一性,同時也延遲了單例的例項化。
類載入時機:JAVA虛擬機器在有且僅有的5種場景下會對類進行初始化。
1.遇到new、getstatic、setstatic或者invokestatic這4個位元組碼指令時,對應的java程式碼場景為:new一個關鍵字或者一個例項化物件時、讀取或設定一個靜態欄位時(final修飾、已在編譯期把結果放入常量池的除外)、呼叫一個類的靜態方法時。
2.使用java.lang.reflect包的方法對類進行反射呼叫的時候,如果類沒進行初始化,需要先呼叫其初始化方法進行初始化。
3.當初始化一個類時,如果其父類還未進行初始化,會先觸發其父類的初始化。
4.當虛擬機器啟動時,使用者需要指定一個要執行的主類(包含main()方法的類),虛擬機器會先初始化這個類。
5.當使用JDK 1.7等動態語言支援時,如果一個java.lang.invoke.MethodHandle例項最後的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法控制代碼,並且這個方法控制代碼所對應的類沒有進行過初始化,則需要先觸發其初始化。
這5種情況被稱為是類的主動引用,注意,這裡《虛擬機器規範》中使用的限定詞是"有且僅有",那麼,除此之外的所有引用類都不會對類進行初始化,稱為被動引用。靜態內部類就屬於被動引用的行列。
我們再回頭看下getInstance()方法,呼叫的是SingleTonHoler.INSTANCE,取的是SingleTonHoler裡的INSTANCE物件,跟上面那個DCL方法不同的是,getInstance()方法並沒有多次去new物件,故不管多少個執行緒去呼叫getInstance()方法,取的都是同一個INSTANCE物件,而不用去重新建立。當getInstance()方法被呼叫時,SingleTonHoler才在SingleTon的執行時常量池裡,把符號引用替換為直接引用,這時靜態物件INSTANCE也真正被建立,然後再被getInstance()方法返回出去,這點同餓漢模式。那麼INSTANCE在建立過程中又是如何保證執行緒安全的呢?在《深入理解JAVA虛擬機器》中,有這麼一句話:
虛擬機器會保證一個類的<clinit>()方法在多執行緒環境中被正確地加鎖、同步,如果多個執行緒同時去初始化一個類,那麼只會有一個執行緒去執行這個類的<clinit>()方法,其他執行緒都需要阻塞等待,直到活動執行緒執行<clinit>()方法完畢。如果在一個類的<clinit>()方法中有耗時很長的操作,就可能造成多個程序阻塞(需要注意的是,其他執行緒雖然會被阻塞,但如果執行<clinit>()方法後,其他執行緒喚醒之後不會再次進入<clinit>()方法。同一個載入器下,一個型別只會初始化一次。),在實際應用中,這種阻塞往往是很隱蔽的。
故而,可以看出INSTANCE在建立過程中是執行緒安全的,所以說靜態內部類形式的單例可保證執行緒安全,也能保證單例的唯一性,同時也延遲了單例的例項化。