單例模式是應用最廣的模式之一。相信大家都非常熟悉了,什么,不熟悉?你都單例模式單刷了二十年了,還不懂?好吧,不懂的同學請自行百度。
首先是最經典的癡漢...不是,餓漢模式:
public class Girlfiriend extends Friend{
private static final Girlfiriend gf=new Girlfiriend ();
private Girlfiriend (){
}
public static Girlfiriend getGirlfriend(){
return gf;
}
}
餓漢模式什么都好,就是有點占地方,每次加載類的時候就存在了。有沒有什么辦法,用的時候在加載呢。沒辦法,世界是被懶人推動前進的,還真有。
懶漢模式:
public class Girlfiriend extends Friend{
private static final Girlfiriend gf;
private Girlfiriend (){
}
public static synchronized Girlfiriend getGirlfriend(){
if(gf==null){
gf= new Girlfriend();
}
return gf;
}
}
這種方法實現了在需要的時候加載。但是注意這里的synchronized關鍵字,synchronized 保證了當進行多線程操作的時候不會產生多個實例。但是這種做法有一個缺點,不管是不是已經存在實例了,都會被鎖阻塞。
那么,有沒有什么辦法解決呢。這個世界是由聰明人改善,所以當然有!
當當當當,DCL模式來了。DCL是不是聽起來特別高大上,其實就是Double Check Lock,說人話就是,你瞅兩次看看到底有沒。
public class Girlfiriend extends Friend{
private static final Girlfiriend gf;
private Girlfiriend (){
}
public static Girlfiriend getGirlfriend(){
if(gf==null){
synchronized(Girlfriend.class){
if(gf==null){
gf= new Girlfriend();
}
}
}
return gf;
}
}
是不是感覺這就結束了。一篇好的文章是要出其不意的。所以,沒完。
DCL并不是十分穩定的,由于java編譯器允許處理器亂序執行,所以這樣做是有隱患的。
簡單說,其實new對象的操作不是原子性的。這句代碼最終會被編譯成多條匯編指令。
(1)給Girlfriend的實例分配內存
(2)調用Girlfriend()的構造函數,初始化成員字段
(3)將gf對象指向分配的內存空間
也就是說這三條指令順序是不可預知的。當另一個線程執行第一個if(gf==null)的時候,由于已經調用了構造函數,但是構造還沒有完成。會把沒有構造完全的對象返回。
那么怎么解決呢,聰明的你一定也能想到,只不過聰明人太多,有人先想到了:
靜態內部類單例模式:
public class Girlfiriend extends Friend{
private Girlfiriend (){
}
public static Girlfiriend getGirlfriend(){
return GirlfriendMother.gf;
}
private static class GirlfriendMother{
private static final Girlfiriend gf= new Girlfiriend ();
}
}
第一次加載Girlfriend的時候,gf不會被初始化,(java類只有被調用的時候才會初始化)。這種方式不僅能夠確保線程的安全,也能夠保證單例對象的唯一性,同時也延遲了單例的實例化。所以這才是推薦使用的單例模式實現的方式。
嘿嘿,你以為到這就完了?too young too naive:
最后是一種單例模式的終極形態——枚舉單例:
public enum Girlfriend{
GIRLFRIEND;
}
默認枚舉實例的創建是線程安全的,并且在任何情況下它都是一個單例。