public class Singleton {
private Singleton() {} //私有構(gòu)造函數(shù)
private static Singleton instance = null; //單例對象
//靜態(tài)工廠方法
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
我們來解釋幾個關(guān)鍵點:
要想讓一個類只能構(gòu)建一個對象,自然不能讓它隨便去做new操作,因此Signleton的構(gòu)造方法是私有的。
instance是Singleton類的靜態(tài)成員,也是我們的單例對象。它的初始值可以寫成Null,也可以寫成new Singleton()。至于其中的區(qū)別后來會做解釋。
getInstance是獲取單例對象的方法。
如果單例初始值是null,還未構(gòu)建,則構(gòu)建單例對象并返回。這個寫法屬于單例模式當(dāng)中的懶漢模式,上面小新寫的就是。
如果單例對象一開始就被new Singleton()主動構(gòu)建,則不再需要判空操作,這種寫法屬于餓漢模式。代碼如下:
public class EagerSingleton(){
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton(){};
public static EagerSingleton getInstance(){
return instance;
}
}

為了實現(xiàn)線程安全,我們一般采用雙重檢測機(jī)制,代碼如下:
public class Singleton {
private Singleton() {} //私有構(gòu)造函數(shù)
private volatile static Singleton instance = null; //單例對象
//靜態(tài)工廠方法
public static Singleton getInstance() {
if (instance == null) { //雙重檢測機(jī)制
synchronized (Singleton.class){ //同步鎖
if (instance == null) { //雙重檢測機(jī)制
instance = new Singleton();
}
}
}
return instance;
}
}
我們來解釋幾個關(guān)鍵點:
1.為了防止new Singleton被執(zhí)行多次,因此在new操作之前加上Synchronized 同步鎖,鎖住整個類(注意,這里不能使用對象鎖)。
2.進(jìn)入Synchronized 臨界區(qū)以后,還要再做一次判空。因為當(dāng)兩個線程同時訪問的時候,線程A構(gòu)建完對象,線程B也已經(jīng)通過了最初的判空驗證,不做第二次判空的話,線程B還是會再次構(gòu)建instance對象。


3、經(jīng)過volatile的修飾,當(dāng)線程A執(zhí)行instance = new Singleton的時候,JVM執(zhí)行順序是什么樣?始終保證是下面的順序:
memory =allocate(); //1:分配對象的內(nèi)存空間
ctorInstance(memory); //2:初始化對象
instance =memory; //3:設(shè)置instance指向剛分配的內(nèi)存地址
如此在線程B看來,instance對象的引用要么指向null,要么指向一個初始化完畢的Instance,而不會出現(xiàn)某個中間態(tài),保證了安全。
volatile的存在保證不發(fā)生指令重排序,從而使得其他線程訪問一個未必初始化完成的類。


用靜態(tài)內(nèi)部類實現(xiàn)單例模式:
public class Singleton {
private static class LazyHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static Singleton getInstance() {
return LazyHolder.INSTANCE;
}
}

利用反射打破單例:
//獲得構(gòu)造器
Constructor con = Singleton.class.getDeclaredConstructor();
//設(shè)置為可訪問
con.setAccessible(true);
//構(gòu)造兩個不同的對象
Singleton singleton1 = (Singleton)con.newInstance();
Singleton singleton2 = (Singleton)con.newInstance();
//驗證是否是不同對象
System.out.println(singleton1.equals(singleton2));
代碼可以簡單歸納為三個步驟:
第一步,獲得單例類的構(gòu)造器。
第二步,把構(gòu)造器設(shè)置為可訪問。
第三步,使用newInstance方法構(gòu)造對象。
最后為了確認(rèn)這兩個對象是否真的是不同的對象,我們使用equals方法進(jìn)行比較。毫無疑問,比較結(jié)果是false。


用枚舉實現(xiàn)單例模式:
public enum SingletonEnum {
INSTANCE;
}
最后總結(jié)一下:
- volatile關(guān)鍵字不但可以防止指令重排,也可以保證線程訪問的變量值是主內(nèi)存中的最新值。
2.使用枚舉實現(xiàn)的單例模式,不但可以防止利用反射強(qiáng)行構(gòu)建單例對象,而且可以在枚舉類對象被反序列化的時候,保證反序列的返回結(jié)果是同一對象。對于其他方式實現(xiàn)的單例模式,如果既想要做到可序列化,又想要反序列化為同一對象,則必須實現(xiàn)readResolve方法。

